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

android button studio 拖拽 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)
    }


}