You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为动态添加并可拖动修改的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:15:02