Android——基于ConstraintLayout实现的可拖拽位置控件
16lz
2021-01-25
最近在研究使用android实现平板和电脑端一些应用的效果,话不多说先上个图
可以看到,实现了中间的绿色区域换到父布局最左侧的功能。在拖动的过程中,父布局会出现上下左右四个箭头按钮,当光标移动到箭头上并放下时,拖动的视图会移动到指定的方向上去。
实现思路:
中间的绿色组件,经历了以下几个过程:
1.长按实现视图的拖拽。
2.拖拽移动过程中,父布局出现四个方向的箭头按钮,当光标在箭头上时显示黄色圆圈表示被选中。
3.选中以后,将拖拽的组件移动到视图的最左侧。
技术难点:
1.首先是如何创造一个拖动的效果。因为ConstraintLayout中我们在布局里已经定义了各个子childview的约束关系,所以直接改变拖动的视图位置是不合适的,这里先隐藏了拖拽的view(设置visible=invisible),然后创建一个imageview来现实这个view的图层,再根据手指光标的移动来展示这个imageview,这样看起来就是一个view被拖走了的效果。实际上并没有移动。
2.其次最大的难点是如何改变ConstraintLayout里的约束关系,这里有一个很重要的技术点就是通过layoutparams得到各个方向的约束对象的id:
var lp: LayoutParams = child.layoutParams as LayoutParamsvar leftToLeftId = lp.leftToLeft
比如要移动中间的view到左边,那么中间的view的左右约束依赖要改成parent和左边,原本左右两个view的依赖也要做相应的修改。约束依赖的修改这里就不赘述了,使用的 ConstraintSet进行修改。
以下是实现的代码,目前并没有实现完整的功能,只是作为一个demo展示:
package com.ng.ui.other.dragimport android.annotation.SuppressLintimport android.content.Contextimport android.graphics.Rectimport android.util.AttributeSetimport android.view.MotionEventimport android.view.Viewimport android.widget.ImageViewimport androidx.constraintlayout.widget.ConstraintLayoutimport androidx.constraintlayout.widget.ConstraintSetimport androidx.core.view.containsimport com.ng.ui.Rimport 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() //当前操作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 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 = 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集成ShareSDK第三方分享和登录
- Android使用Gallery实现照片拖动的特效
- Android之自定义最简单的竖向引导页
- Android(安卓)利用ViewPager+GridView,仿美团首页导航栏分类布局
- android listview仿iphone弹簧特效
- Android使用底部导航2018-08-16
- Android(安卓)Configuration change引发的问题及解决方法
- Android实现多个跑马灯效果,多个文本框TextView的跑马灯
- Android(安卓)UI 的几个简单技巧