如何为动态添加并可拖动修改的View设置碰撞边界
解决拖动View时避免越过其他View边界的问题
动态添加的View可通过拖动修改X/Y坐标,但拖动时会越过其他View的边界,需要实现碰撞后停止拖动的逻辑。
现有Kotlin代码
private fun drawobject(context: Context?) { val r: Resources = getResources() val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 100F, r.getDisplayMetrics()).toInt() // Convert mm to px val objectconteiner: LinearLayout = requireView().findViewById(R.id.progressBar_container) var ViewLayoutParams: ViewGroup.LayoutParams = ViewGroup.LayoutParams( 100, 20) var imageView:View = View(context) imageView.setLayoutParams(ViewLayoutParams) imageView.setBackgroundColor(Color.RED) var initialX:Float? = null var initialY:Float? = null var initialTouchX:Float? = null var initialTouchY:Float? = null imageView.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { initialX = event!!.getX() initialY = event!!.getY() imageView.setBackgroundColor(Color.BLACK) //Log.e("211", "$initialX") } MotionEvent.ACTION_MOVE -> { binding.Hsc.setSwipeable(false) //ban on scrollview movement binding.Vsc.setSwipeable(false) initialTouchX = (event!!.getX() - initialX!!) initialTouchY = (event!!.getY() - initialY!!) var actualX = imageView.x + initialTouchX!! var actualY = imageView.y + initialTouchY!! imageView.setX(actualX!!) imageView.setY(actualY!!) //Log.e("2", "$px") //Log.e("2", "$actualX") } MotionEvent.ACTION_UP -> { //Log.e("2", "gh") binding.Hsc.setSwipeable(true) binding.Vsc.setSwipeable(true) } } true } imageView.setBackgroundColor(Color.RED) objectconteiner.addView(imageView) }
布局XML代码
<LinearLayout android:orientation="horizontal" android:id="@+id/progressBar_container" android:layout_width="100mm" android:layout_height="100sp" android:layout_marginTop="100sp" android:layout_marginLeft="100sp" android:background="@color/back"> </LinearLayout>
解决方案
核心思路是在计算新坐标后,先限制View不能超出容器边界,再检测与其他View的碰撞,调整坐标至刚好不重叠的位置。
修改后的完整代码
private fun drawobject(context: Context?) { val r: Resources = getResources() val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 100F, r.displayMetrics).toInt() // Convert mm to px val objectContainer: LinearLayout = requireView().findViewById(R.id.progressBar_container) val viewLayoutParams: ViewGroup.LayoutParams = ViewGroup.LayoutParams(100, 20) val imageView: View = View(context).apply { layoutParams = viewLayoutParams setBackgroundColor(Color.RED) } var initialX: Float? = null var initialY: Float? = null imageView.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { initialX = event.x initialY = event.y imageView.setBackgroundColor(Color.BLACK) } MotionEvent.ACTION_MOVE -> { binding.Hsc.setSwipeable(false) binding.Vsc.setSwipeable(false) // 计算触摸偏移量 val touchOffsetX = event.x - initialX!! val touchOffsetY = event.y - initialY!! // 计算未限制的目标坐标 var targetX = imageView.x + touchOffsetX var targetY = imageView.y + touchOffsetY // 1. 限制在容器边界内 targetX = clampToContainerX(targetX, imageView.width, objectContainer) targetY = clampToContainerY(targetY, imageView.height, objectContainer) // 2. 检测并修正与其他View的碰撞 targetX = resolveHorizontalCollisions(targetX, imageView, objectContainer) targetY = resolveVerticalCollisions(targetY, imageView, objectContainer) // 设置最终坐标 imageView.x = targetX imageView.y = targetY // 更新初始触摸点,避免拖动偏移累积 initialX = event.x initialY = event.y } MotionEvent.ACTION_UP -> { binding.Hsc.setSwipeable(true) binding.Vsc.setSwipeable(true) imageView.setBackgroundColor(Color.RED) } } true } objectContainer.addView(imageView) } // 限制X坐标在容器范围内 private fun clampToContainerX(targetX: Float, viewWidth: Int, container: ViewGroup): Float { val containerLeft = 0f val containerRight = container.width.toFloat() return targetX.coerceIn(containerLeft, containerRight - viewWidth) } // 限制Y坐标在容器范围内 private fun clampToContainerY(targetY: Float, viewHeight: Int, container: ViewGroup): Float { val containerTop = 0f val containerBottom = container.height.toFloat() return targetY.coerceIn(containerTop, containerBottom - viewHeight) } // 处理水平方向的View碰撞 private fun resolveHorizontalCollisions(targetX: Float, currentView: View, container: ViewGroup): Float { var adjustedX = targetX val currentRect = Rect().apply { left = adjustedX.toInt() right = adjustedX.toInt() + currentView.width top = currentView.y.toInt() bottom = currentView.y.toInt() + currentView.height } // 遍历容器内所有其他View for (i in 0 until container.childCount) { val otherView = container.getChildAt(i) if (otherView == currentView) continue val otherRect = Rect() otherView.getHitRect(otherRect) // 检测是否发生水平重叠 if (Rect.intersects(currentRect, otherRect)) { adjustedX = if (adjustedX < otherView.x) { // 当前View左移,停在目标View左侧 otherView.x - currentView.width } else { // 当前View右移,停在目标View右侧 otherView.x + otherView.width } // 更新当前View的矩形位置,避免重复碰撞 currentRect.left = adjustedX.toInt() currentRect.right = adjustedX.toInt() + currentView.width } } return adjustedX } // 处理垂直方向的View碰撞 private fun resolveVerticalCollisions(targetY: Float, currentView: View, container: ViewGroup): Float { var adjustedY = targetY val currentRect = Rect().apply { left = currentView.x.toInt() right = currentView.x.toInt() + currentView.width top = adjustedY.toInt() bottom = adjustedY.toInt() + currentView.height } // 遍历容器内所有其他View for (i in 0 until container.childCount) { val otherView = container.getChildAt(i) if (otherView == currentView) continue val otherRect = Rect() otherView.getHitRect(otherRect) // 检测是否发生垂直重叠 if (Rect.intersects(currentRect, otherRect)) { adjustedY = if (adjustedY < otherView.y) { // 当前View上移,停在目标View上方 otherView.y - currentView.height } else { // 当前View下移,停在目标View下方 otherView.y + otherView.height } // 更新当前View的矩形位置,避免重复碰撞 currentRect.top = adjustedY.toInt() currentRect.bottom = adjustedY.toInt() + currentView.height } } return adjustedY }
关键说明
- 容器边界限制:使用
coerceIn方法确保View不会超出容器的左右、上下边界。 - 碰撞检测:通过
Rect.intersects判断当前View与其他View的矩形区域是否重叠,根据移动方向调整坐标至刚好不接触的位置。 - 偏移量更新:在ACTION_MOVE中实时更新初始触摸点,避免连续拖动时的坐标偏移累积问题。
内容的提问来源于stack exchange,提问作者doker797
相关产品推荐
相关产品推荐

