Android 控件拖拽

2 阅读2分钟

安卓实现页面控件拖拽

先来个布局文件吧!

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 其他原有布局保持不变 -->
    
    <!-- 可拖拽控件 - 只能吸附在左右两边 -->
    <LinearLayout
        android:id="@+id/llDragView"
        android:layout_width="100dp"
        android:layout_height="@dimen/dp20"
        android:background="@color/theme_color"
        android:orientation="horizontal"
        android:clickable="true"
        android:focusable="true"
        android:gravity="center"
        android:elevation="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:src="@drawable/ic_drag_handle"
            android:layout_marginEnd="4dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="拖拽"
            android:textColor="@android:color/white"
            android:textSize="10sp" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

页面代码功能实现

class DragFragment : Fragment() {
    
    private var dX = 0f
    private var dY = 0f
    private var lastX = 0f
    private var lastY = 0f
    private var isDragging = false
    
    // 吸附边缘的偏移量(距离屏幕边缘的距离),也可以是0,如果是0,则贴附在屏幕边上
    private val edgeOffset = 16.dpToPx()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        setupDragView()
        // 初始化时吸附到左边
        initDragViewPosition()
    }
    
    private fun initDragViewPosition() {
        val dragView = view?.findViewById<LinearLayout>(R.id.llDragView) ?: return
        val parentView = dragView.parent as View
        
        parentView.post {
            // 默认吸附到左边
            dragView.x = edgeOffset.toFloat()
            // 垂直居中
            dragView.y = (parentView.height / 2 - dragView.height / 2).toFloat()
        }
    }
    
    private fun setupDragView() {
        val dragView = view?.findViewById<LinearLayout>(R.id.llDragView) ?: return
        val parentView = dragView.parent as View
        
        dragView.setOnTouchListener { view, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    dX = view.x - event.rawX
                    dY = view.y - event.rawY
                    lastX = event.rawX
                    lastY = event.rawY
                    isDragging = false
                    true
                }
                MotionEvent.ACTION_MOVE -> {
                    val newX = event.rawX + dX
                    val newY = event.rawY + dY
                    
                    // 只限制Y轴范围,X轴不限制(让用户可以拖动到任意X位置)
                    val maxY = parentView.height - view.height
                    val finalY = when {
                        newY < 0 -> 0f
                        newY > maxY -> maxY.toFloat()
                        else -> newY
                    }
                    
                    view.y = finalY
                    view.x = newX
                    
                    // 判断是否在拖拽
                    val deltaX = Math.abs(event.rawX - lastX)
                    val deltaY = Math.abs(event.rawY - lastY)
                    
                    if (deltaX > 10 || deltaY > 10) {
                        isDragging = true
                        view.parent.requestDisallowInterceptTouchEvent(true)
                    }
                    
                    true
                }
                MotionEvent.ACTION_UP -> {
                    // 拖拽结束时吸附到最近的边缘
                    if (isDragging) {
                        snapToEdge(dragView)
                    } else {
                        // 点击事件
                        onDragViewClick()
                    }
                    
                    view.parent.requestDisallowInterceptTouchEvent(false)
                    isDragging = false
                    true
                }
                else -> false
            }
        }
    }
    
    private fun snapToEdge(view: View) {
        val parentView = view.parent as View
        val viewCenterX = view.x + view.width / 2
        val parentCenterX = parentView.width / 2
        
        // 判断应该吸附到左边还是右边
        val targetX = if (viewCenterX < parentCenterX) {
            // 吸附到左边
            edgeOffset.toFloat()
        } else {
            // 吸附到右边,如果只想吸附到左边,则去掉if判断即可
            (parentView.width - view.width - edgeOffset).toFloat()
        }
        
        // 添加平滑动画
        view.animate()
            .x(targetX)
            .setDuration(200)
            .start()
    }
    
    private fun onDragViewClick() {
        // 处理拖拽控件的点击事件
        // 例如:显示隐藏其他内容、刷新数据等
        Toast.makeText(context, "拖拽控件被点击", Toast.LENGTH_SHORT).show()
    }
    
    // dp转px的扩展函数
    private fun Int.dpToPx(): Int {
        return (this * resources.displayMetrics.density).toInt()
    }
}