安卓实现页面控件拖拽
先来个布局文件吧!
<?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()
}
}