小言_互联网的博客

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

560人阅读  评论(0)

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

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

实现思路:

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

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

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

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

技术难点:

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

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


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

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

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


  
  1. package com.ng.ui.other.drag
  2. import android.annotation.SuppressLint
  3. import android.content.Context
  4. import android.graphics.Rect
  5. import android.util.AttributeSet
  6. import android.view.MotionEvent
  7. import android.view.View
  8. import android.widget.ImageView
  9. import androidx.constraintlayout.widget.ConstraintLayout
  10. import androidx.constraintlayout.widget.ConstraintSet
  11. import androidx.core.view.contains
  12. import com.ng.ui.R
  13. import kotlinx.android.synthetic.main.activity_drag.view.*
  14. import java.util.*
  15. /**
  16. * 描述: 可拖动layout
  17. * @author Jzn
  18. * @date 2020/9/8
  19. */
  20. class ZLayout : ConstraintLayout,ZLayoutStretchListener {
  21. //子layout列表
  22. private var mChildLayoutList = arrayListOf<ZChildLayout>()
  23. //当前操作view
  24. private var mOperationView: ConstraintLayout? = null
  25. private var mOperationIndex: Int = - 1
  26. //绘制
  27. private var mIsDrawing = false
  28. private lateinit var mAnimItemView: ImageView
  29. //操作符
  30. private lateinit var mLeftArrow: ImageView
  31. private lateinit var mRightArrow: ImageView
  32. private lateinit var mUpArrow: ImageView
  33. private lateinit var mDownArrow: ImageView
  34. private lateinit var mArrowList: ArrayList<ImageView>
  35. private var mArrowResList = arrayListOf(R.drawable.ic_left, R.drawable.ic_up, R.drawable.ic_right, R.drawable.ic_down)
  36. //操作符区域
  37. private var mLeftRect: Rect = Rect()
  38. private var mRightRect: Rect = Rect()
  39. private var mUpRect: Rect = Rect()
  40. private var mDownRect: Rect = Rect()
  41. private var mArrowRectList: ArrayList<Rect> = arrayListOf()
  42. //父布局id
  43. private var mRootId = 0
  44. //
  45. constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
  46. initAll()
  47. }
  48. private fun initAll() {
  49. mRootId = id
  50. mAnimItemView = ImageView(context)
  51. mAnimItemView.scaleType = ImageView.ScaleType.FIT_XY
  52. mLeftArrow = ImageView(context)
  53. mRightArrow = ImageView(context)
  54. mUpArrow = ImageView(context)
  55. mDownArrow = ImageView(context)
  56. mLeftArrow.setImageResource(mArrowResList[ 0])
  57. mUpArrow.setImageResource(mArrowResList[ 1])
  58. mRightArrow.setImageResource(mArrowResList[ 2])
  59. mDownArrow.setImageResource(mArrowResList[ 3])
  60. mArrowList = arrayListOf(mLeftArrow, mUpArrow, mRightArrow, mDownArrow)
  61. mArrowRectList = arrayListOf(mLeftRect, mUpRect, mRightRect, mDownRect)
  62. }
  63. @SuppressLint("DrawAllocation")
  64. override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
  65. super.onLayout(changed, left, top, right, bottom)
  66. var mArrowWidth = mLeftArrow.width
  67. var mArrowHeight = mLeftArrow.height
  68. //确定操作符位置
  69. mLeftArrow.x = 0f
  70. mLeftArrow.y = height / 2.toFloat() - mArrowHeight / 2
  71. mRightArrow.x = width.toFloat() - mArrowWidth
  72. mRightArrow.y = height / 2.toFloat() - mArrowHeight / 2
  73. mUpArrow.x = width / 2.toFloat() - mArrowWidth / 2
  74. mUpArrow.y = 0f
  75. mDownArrow.x = width / 2.toFloat() - mArrowWidth / 2
  76. mDownArrow.y = height.toFloat() - mArrowHeight
  77. //确定操作符区域
  78. //扩大一点区域,方便选中
  79. mArrowWidth += 100
  80. mArrowHeight += 100
  81. mLeftRect = Rect( 0, height / 2 - mArrowHeight / 2, mArrowWidth, height / 2 + mArrowHeight / 2)
  82. mRightRect = Rect(width - mArrowWidth, height / 2 - mArrowHeight / 2, width, height / 2 + mArrowHeight / 2)
  83. mUpRect = Rect(width / 2 - mArrowWidth / 2, 0, width / 2 + mArrowWidth / 2, mArrowHeight)
  84. mDownRect = Rect(width / 2 - mArrowWidth / 2, height - mArrowHeight, width / 2 + mArrowWidth / 2, height)
  85. mArrowRectList = arrayListOf(mLeftRect, mUpRect, mRightRect, mDownRect)
  86. }
  87. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  88. super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  89. val childCount = childCount
  90. mChildLayoutList.clear()
  91. for (i in 0 until childCount) {
  92. val childView: View = getChildAt(i)
  93. if (childView is ZChildLayout) {
  94. mChildLayoutList.add(childView)
  95. }
  96. childView.measure(widthMeasureSpec, heightMeasureSpec)
  97. }
  98. mChildLayoutList.forEachIndexed { index, child ->
  99. child.setCallBack(index, this)
  100. }
  101. }
  102. //拖动的位置
  103. private var mLiftX = 0f
  104. private var mLiftY = 0f
  105. //起始点位置
  106. private var mStartX = 0f
  107. private var mStartY = 0f
  108. //位移
  109. private var mIntervalX = 0f
  110. private var mIntervalY = 0f
  111. override fun onStartLift(motionEvent: MotionEvent) {
  112. mStartX = motionEvent.x
  113. mStartY = motionEvent.y
  114. mChildLayoutList.forEachIndexed { index, child ->
  115. }
  116. }
  117. override fun onLift(index: Int, view: View, motionEvent: MotionEvent) {
  118. //MLog.d("$index $motionEvent")
  119. mOperationView = view as ConstraintLayout
  120. mLiftX = motionEvent.rawX
  121. mLiftY = motionEvent.rawY - ViewUtils.getStatusBarHeight(context)
  122. mIntervalX = mStartX - motionEvent.x
  123. mIntervalY = mStartY - motionEvent.y
  124. val bitmap = ViewUtils.getBitmapFromView(view)
  125. view.visibility = View.INVISIBLE
  126. val tarGetLocation = IntArray( 2)
  127. view.getLocationOnScreen(tarGetLocation)
  128. if (!mIsDrawing) {
  129. mAnimItemView.setImageBitmap(bitmap)
  130. }
  131. mAnimItemView.x = tarGetLocation[ 0].toFloat() - mIntervalX
  132. mAnimItemView.y = tarGetLocation[ 1].toFloat() - ViewUtils.getStatusBarHeight(context) - mIntervalY
  133. //显示悬浮框
  134. showView(mAnimItemView)
  135. mIsDrawing = true
  136. //显示操作视图
  137. showOptionView()
  138. }
  139. //在父布局的四个角显示操作符按钮
  140. private fun showOptionView() {
  141. mArrowList.forEach {
  142. showView(it)
  143. }
  144. mArrowRectList.forEachIndexed { index, rect ->
  145. if (rect.contains(mLiftX.toInt(), mLiftY.toInt())) {
  146. mArrowList[index].setImageResource(R.drawable.ic_change)
  147. mOperationIndex = index
  148. } else {
  149. mArrowList[index].setImageResource(mArrowResList[index])
  150. }
  151. }
  152. }
  153. //重新排序
  154. //先试验单一的左右关系
  155. private fun onOption(index: Int) {
  156. var constraintSet: ConstraintSet = ConstraintSet()
  157. constraintSet.clone(root_layout)
  158. //左 上 右 下
  159. when (index) {
  160. 0 -> {
  161. //原来的左右改为到一起
  162. //找到原来左边约束父布局的view
  163. // mChildLayoutList.forEachIndexed { index, child ->
  164. // var lp: LayoutParams = child.layoutParams as LayoutParams
  165. // var leftToLeftId = lp.leftToLeft
  166. //
  167. // MLog.d("index: " + index)
  168. //
  169. // MLog.d(" left to left : " + lp.leftToLeft)
  170. // MLog.d(" left to right : " + lp.leftToRight)
  171. //
  172. // MLog.d(" right to left : " + lp.rightToLeft)
  173. // MLog.d(" right to right : " + lp.rightToRight)
  174. //
  175. // if (leftToLeftId.equals(0)) {
  176. // MLog.d("当前操作的:" + mOperationView!!.id)
  177. // MLog.d(" 找到的 : " + child.id)
  178. //
  179. // }
  180. //
  181. // //left to parent
  182. // constraintSet.connect(mOperationView!!.id,ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT,20)
  183. // constraintSet.connect(mOperationView!!.id,ConstraintSet.RIGHT,child.id,ConstraintSet.LEFT,20)
  184. // constraintSet.applyTo(root_layout)
  185. // return
  186. //
  187. // }
  188. var left = mChildLayoutList[ 0]
  189. var center = mChildLayoutList[ 1]
  190. var right = mChildLayoutList[ 2]
  191. constraintSet.connect(center.id,ConstraintSet.LEFT,ConstraintSet.PARENT_ID,ConstraintSet.LEFT, 0)
  192. constraintSet.connect(center.id,ConstraintSet.RIGHT,left.id,ConstraintSet.LEFT, 0)
  193. constraintSet.connect(left.id,ConstraintSet.LEFT,center.id,ConstraintSet.RIGHT, 0)
  194. constraintSet.connect(left.id,ConstraintSet.RIGHT,right.id,ConstraintSet.LEFT, 0)
  195. constraintSet.connect(right.id,ConstraintSet.LEFT,left.id,ConstraintSet.RIGHT, 0)
  196. constraintSet.connect(right.id,ConstraintSet.RIGHT,ConstraintSet.PARENT_ID,ConstraintSet.RIGHT, 0)
  197. constraintSet.applyTo(root_layout)
  198. }
  199. 1 -> {
  200. }
  201. 2 -> {
  202. }
  203. 3 -> {
  204. }
  205. }
  206. //完成以后重置mOperationIndex
  207. mOperationIndex = - 1
  208. }
  209. //在父布局的四个角显示操作符按钮
  210. private fun hideOptionView() {
  211. mArrowList.forEach {
  212. hideView(it)
  213. }
  214. }
  215. override fun onFinishLift() {
  216. mIsDrawing = false
  217. //判断此时的状态
  218. hideView(mAnimItemView)
  219. hideOptionView()
  220. if (mOperationView != null) {
  221. mOperationView!!.visibility = View.VISIBLE
  222. }
  223. if (mOperationIndex != - 1) {
  224. onOption(mOperationIndex)
  225. }
  226. }
  227. //移除view
  228. private fun hideView(view: View) {
  229. if ( contains(view)) {
  230. removeView(view)
  231. }
  232. }
  233. //显示view
  234. private fun showView(view: View) {
  235. val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
  236. hideView(view)
  237. addView(view, lp)
  238. }
  239. }

 


转载:https://blog.csdn.net/qq_22770457/article/details/108505605
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场