Android——基于ConstraintLayout实现的可拖拽位置控件

Posted 化作孤岛的瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——基于ConstraintLayout实现的可拖拽位置控件相关的知识,希望对你有一定的参考价值。

最近在研究使用android实现平板和电脑端一些应用的效果,话不多说先上个图

可以看到,实现了中间的绿色区域换到父布局最左侧的功能。在拖动的过程中,父布局会出现上下左右四个箭头按钮,当光标移动到箭头上并放下时,拖动的视图会移动到指定的方向上去。

实现思路:

中间的绿色组件,经历了以下几个过程:

1.长按实现视图的拖拽。

2.拖拽移动过程中,父布局出现四个方向的箭头按钮,当光标在箭头上时显示黄色圆圈表示被选中。

3.选中以后,将拖拽的组件移动到视图的最左侧。

技术难点:

1.首先是如何创造一个拖动的效果。因为ConstraintLayout中我们在布局里已经定义了各个子childview的约束关系,所以直接改变拖动的视图位置是不合适的,这里先隐藏了拖拽的view(设置visible=invisible),然后创建一个imageview来现实这个view的图层,再根据手指光标的移动来展示这个imageview,这样看起来就是一个view被拖走了的效果。实际上并没有移动。

2.其次最大的难点是如何改变ConstraintLayout里的约束关系,这里有一个很重要的技术点就是通过layoutparams得到各个方向的约束对象的id:

var lp: LayoutParams = child.layoutParams as LayoutParams
var leftToLeftId = lp.leftToLeft

比如要移动中间的view到左边,那么中间的view的左右约束依赖要改成parent和左边,原本左右两个view的依赖也要做相应的修改。约束依赖的修改这里就不赘述了,使用的 ConstraintSet进行修改。

以下是实现的代码,目前并没有实现完整的功能,只是作为一个demo展示:

package com.ng.ui.other.drag

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.contains
import com.ng.ui.R
import kotlinx.android.synthetic.main.activity_drag.view.*
import java.util.*

/**
 * 描述:  可拖动layout
 * @author Jzn
 * @date 2020/9/8
 */
class ZLayout : ConstraintLayout,ZLayoutStretchListener 
    //子layout列表
    private var mChildLayoutList = arrayListOf<ZChildLayout>()

    //当前操作view
    private var mOperationView: ConstraintLayout? = null
    private var mOperationIndex: Int = -1

    //绘制
    private var mIsDrawing = false
    private lateinit var mAnimItemView: ImageView

    //操作符
    private lateinit var mLeftArrow: ImageView
    private lateinit var mRightArrow: ImageView
    private lateinit var mUpArrow: ImageView
    private lateinit var mDownArrow: ImageView
    private lateinit var mArrowList: ArrayList<ImageView>
    private var mArrowResList = arrayListOf(R.drawable.ic_left, R.drawable.ic_up, R.drawable.ic_right, R.drawable.ic_down)

    //操作符区域
    private var mLeftRect: Rect = Rect()
    private var mRightRect: Rect = Rect()
    private var mUpRect: Rect = Rect()
    private var mDownRect: Rect = Rect()
    private var mArrowRectList: ArrayList<Rect> = arrayListOf()

    //父布局id
    private var mRootId = 0

    //

    constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) 
        initAll()
    

    private fun initAll() 
        mRootId = id
        mAnimItemView = ImageView(context)
        mAnimItemView.scaleType = ImageView.ScaleType.FIT_XY
        mLeftArrow = ImageView(context)
        mRightArrow = ImageView(context)
        mUpArrow = ImageView(context)
        mDownArrow = ImageView(context)
        mLeftArrow.setImageResource(mArrowResList[0])
        mUpArrow.setImageResource(mArrowResList[1])
        mRightArrow.setImageResource(mArrowResList[2])
        mDownArrow.setImageResource(mArrowResList[3])
        mArrowList = arrayListOf(mLeftArrow, mUpArrow, mRightArrow, mDownArrow)
        mArrowRectList = arrayListOf(mLeftRect, mUpRect, mRightRect, mDownRect)
    

    @SuppressLint("DrawAllocation")
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) 
        super.onLayout(changed, left, top, right, bottom)
        var mArrowWidth = mLeftArrow.width
        var mArrowHeight = mLeftArrow.height
        //确定操作符位置
        mLeftArrow.x = 0f
        mLeftArrow.y = height / 2.toFloat() - mArrowHeight / 2
        mRightArrow.x = width.toFloat() - mArrowWidth
        mRightArrow.y = height / 2.toFloat() - mArrowHeight / 2
        mUpArrow.x = width / 2.toFloat() - mArrowWidth / 2
        mUpArrow.y = 0f
        mDownArrow.x = width / 2.toFloat() - mArrowWidth / 2
        mDownArrow.y = height.toFloat() - mArrowHeight
        //确定操作符区域
        //扩大一点区域,方便选中
        mArrowWidth += 100
        mArrowHeight += 100
        mLeftRect = Rect(0, height / 2 - mArrowHeight / 2, mArrowWidth, height / 2 + mArrowHeight / 2)
        mRightRect = Rect(width - mArrowWidth, height / 2 - mArrowHeight / 2, width, height / 2 + mArrowHeight / 2)
        mUpRect = Rect(width / 2 - mArrowWidth / 2, 0, width / 2 + mArrowWidth / 2, mArrowHeight)
        mDownRect = Rect(width / 2 - mArrowWidth / 2, height - mArrowHeight, width / 2 + mArrowWidth / 2, height)
        mArrowRectList = arrayListOf(mLeftRect, mUpRect, mRightRect, mDownRect)

    

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val childCount = childCount
        mChildLayoutList.clear()
        for (i in 0 until childCount) 
            val childView: View = getChildAt(i)
            if (childView is ZChildLayout) 
                mChildLayoutList.add(childView)
            
            childView.measure(widthMeasureSpec, heightMeasureSpec)
        



        mChildLayoutList.forEachIndexed  index, child ->
            child.setCallBack(index, this)
        
    

    //拖动的位置
    private var mLiftX = 0f
    private var mLiftY = 0f

    //起始点位置
    private var mStartX = 0f
    private var mStartY = 0f

    //位移
    private var mIntervalX = 0f
    private var mIntervalY = 0f

    override fun onStartLift(motionEvent: MotionEvent) 
        mStartX = motionEvent.x
        mStartY = motionEvent.y

        mChildLayoutList.forEachIndexed  index, child ->
        
    


    override fun onLift(index: Int, view: View, motionEvent: MotionEvent) 
        //MLog.d("$index $motionEvent")
        mOperationView = view as ConstraintLayout
        mLiftX = motionEvent.rawX
        mLiftY = motionEvent.rawY - ViewUtils.getStatusBarHeight(context)
        mIntervalX = mStartX - motionEvent.x
        mIntervalY = mStartY - motionEvent.y

        val bitmap = ViewUtils.getBitmapFromView(view)
        view.visibility = View.INVISIBLE
        val tarGetLocation = IntArray(2)
        view.getLocationOnScreen(tarGetLocation)
        if (!mIsDrawing) 
            mAnimItemView.setImageBitmap(bitmap)
        
        mAnimItemView.x = tarGetLocation[0].toFloat() - mIntervalX
        mAnimItemView.y = tarGetLocation[1].toFloat() - ViewUtils.getStatusBarHeight(context) - mIntervalY
        //显示悬浮框
        showView(mAnimItemView)
        mIsDrawing = true

        //显示操作视图
        showOptionView()

    

    //在父布局的四个角显示操作符按钮
    private fun showOptionView() 
        mArrowList.forEach 
            showView(it)
        
        mArrowRectList.forEachIndexed  index, rect ->
            if (rect.contains(mLiftX.toInt(), mLiftY.toInt())) 
                mArrowList[index].setImageResource(R.drawable.ic_change)
                mOperationIndex = index
             else 
                mArrowList[index].setImageResource(mArrowResList[index])
            
        
    

    //重新排序
    //先试验单一的左右关系
    private fun onOption(index: Int) 
        var constraintSet: ConstraintSet = ConstraintSet()
        constraintSet.clone(root_layout)

        //左 上 右 下
        when (index) 
            0 -> 
                //原来的左右改为到一起
                //找到原来左边约束父布局的view
//                mChildLayoutList.forEachIndexed  index, child ->
//                    var lp: LayoutParams = child.layoutParams as LayoutParams
//                    var leftToLeftId = lp.leftToLeft
//
//                    MLog.d("index: " + index)
//
//                    MLog.d(" left to left : " + lp.leftToLeft)
//                    MLog.d(" left to right : " + lp.leftToRight)
//
//                    MLog.d(" right to left : " + lp.rightToLeft)
//                    MLog.d(" right to right : " + lp.rightToRight)
//
//                    if (leftToLeftId.equals(0)) 
//                        MLog.d("当前操作的:" + mOperationView!!.id)
//                        MLog.d(" 找到的 : " + child.id)
//
//                    
//
//                    //left to parent
//                    constraintSet.connect(mOperationView!!.id,ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,20)
//                    constraintSet.connect(mOperationView!!.id,ConstraintSet.RIGHT,child.id,ConstraintSet.LEFT,20)
//                    constraintSet.applyTo(root_layout)
//                    return
//
//                
                var left = mChildLayoutList[0]
                var center = mChildLayoutList[1]
                var right = mChildLayoutList[2]
                constraintSet.connect(center.id,ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,0)
                constraintSet.connect(center.id,ConstraintSet.RIGHT,left.id,ConstraintSet.LEFT,0)

                constraintSet.connect(left.id,ConstraintSet.LEFT,center.id,ConstraintSet.RIGHT,0)
                constraintSet.connect(left.id,ConstraintSet.RIGHT,right.id,ConstraintSet.LEFT,0)

                constraintSet.connect(right.id,ConstraintSet.LEFT,left.id,ConstraintSet.RIGHT,0)
                constraintSet.connect(right.id,ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT,0)

                constraintSet.applyTo(root_layout)
            
            1 -> 
            
            2 -> 
            
            3 -> 
            
        
        //完成以后重置mOperationIndex
        mOperationIndex = -1
    
    //在父布局的四个角显示操作符按钮
    private fun hideOptionView() 
        mArrowList.forEach 
            hideView(it)
        
    

    override fun onFinishLift() 
        mIsDrawing = false
        //判断此时的状态
        hideView(mAnimItemView)
        hideOptionView()
        if (mOperationView != null) 
            mOperationView!!.visibility = View.VISIBLE
        
        if (mOperationIndex != -1) 
            onOption(mOperationIndex)
        
    

    //移除view
    private fun hideView(view: View) 
        if ( contains(view)) 
            removeView(view)
        
    


    //显示view
    private fun showView(view: View) 
        val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        hideView(view)
        addView(view, lp)
    


 

以上是关于Android——基于ConstraintLayout实现的可拖拽位置控件的主要内容,如果未能解决你的问题,请参考以下文章

如何将约束布局包含到另一个约束布局并在每个约束布局之间设置约束

ConstraintLayout2.0一篇写不完之嵌套滚动怎么滚

Android事件处理

Android手机是基于linux的,可以当做一般的linux电脑用吗

基于android-async-http的android服务

基于android的简单计算器