用400行代码手写一个通用悬浮 View,玩转随意拖拽和可吸边
2023-09-14 06:28:40
自定义悬浮 View:轻松实现拖拽和吸边功能
在移动应用开发中,悬浮 View 是一种常见且实用的 UI 元素,它允许用户随意拖动和放置在屏幕上。市面上虽然有现成的悬浮 View 库,但自己动手实现一个可以加深对底层原理的理解,还能根据需求定制更多功能。本文将手把手教你用不到 400 行代码实现一个通用的悬浮 View,助你轻松实现拖拽和吸边功能。
布局分析
悬浮 View 一般由两部分组成:
- 拖拽区域: 用户可以点击并拖动此区域来移动悬浮 View。
- 内容区域: 悬浮 View 的主内容区域,放置需要展示的控件。
根据这个布局,我们可以先定义一个 FrameLayout 作为根布局,并将拖拽区域和内容区域分别定义为 View 和 FrameLayout。
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:id="@+id/drag_view"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@color/drag_area_color" />
<FrameLayout
android:id="@+id/content_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content_area_color" />
</FrameLayout>
触摸事件处理
悬浮 View 的移动依赖于对触摸事件的处理。我们需要监听用户的触摸动作,并根据手指的移动更新悬浮 View 的位置。
class FloatingView(context: Context) : FrameLayout(context) {
// 触摸相关变量
private var downX = 0f
private var downY = 0f
private var touchSlop = 0
init {
// 获取手势容差值
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
// 监听触摸事件
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 记录按下的位置
downX = event.rawX
downY = event.rawY
true
}
MotionEvent.ACTION_MOVE -> {
// 计算移动的偏移量
val dx = event.rawX - downX
val dy = event.rawY - downY
// 如果移动距离超过手势容差,则更新位置
if (Math.abs(dx) > touchSlop || Math.abs(dy) > touchSlop) {
updatePosition(dx, dy)
downX = event.rawX
downY = event.rawY
}
true
}
else -> false
}
}
}
// 更新悬浮 View 位置
private fun updatePosition(dx: Float, dy: Float) {
val left = (x + dx).toInt()
val top = (y + dy).toInt()
val right = left + width
val bottom = top + height
// 检查边界,防止悬浮 View 超出屏幕
if (left < 0) {
left = 0
right = width
} else if (right > displayWidth) {
right = displayWidth
left = right - width
}
if (top < 0) {
top = 0
bottom = height
} else if (bottom > displayHeight) {
bottom = displayHeight
top = bottom - height
}
x = left.toFloat()
y = top.toFloat()
}
}
边缘吸附
当用户松开手指时,悬浮 View 应该吸附到屏幕边缘。我们可以通过计算当前位置和屏幕边缘的距离,来判断应该吸附到哪个边缘。
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_UP) {
val screenWidth = resources.displayMetrics.widthPixels
val screenHeight = resources.displayMetrics.heightPixels
val left = x.toInt()
val right = left + width
val top = y.toInt()
val bottom = top + height
// 计算到各个边缘的距离
val distanceToLeft = left
val distanceToRight = screenWidth - right
val distanceToTop = top
val distanceToBottom = screenHeight - bottom
// 判断吸附到哪个边缘
val minDistance = minOf(distanceToLeft, distanceToRight, distanceToTop, distanceToBottom)
when {
minDistance == distanceToLeft -> x = 0f
minDistance == distanceToRight -> x = (screenWidth - width).toFloat()
minDistance == distanceToTop -> y = 0f
minDistance == distanceToBottom -> y = (screenHeight - height).toFloat()
}
}
return super.onTouchEvent(event)
}
示例代码
将以上代码整合到一起,就可以实现一个通用的悬浮 View:
class FloatingView(context: Context) : FrameLayout(context) {
// ... 省略其他代码 ...
override fun onTouchEvent(event: MotionEvent?): Boolean {
// ... 省略其他代码 ...
if (event?.action == MotionEvent.ACTION_UP) {
val screenWidth = resources.displayMetrics.widthPixels
val screenHeight = resources.displayMetrics.heightPixels
val left = x.toInt()
val right = left + width
val top = y.toInt()
val bottom = top + height
// 计算到各个边缘的距离
val distanceToLeft = left
val distanceToRight = screenWidth - right
val distanceToTop = top
val distanceToBottom = screenHeight - bottom
// 判断吸附到哪个边缘
val minDistance = minOf(distanceToLeft, distanceToRight, distanceToTop, distanceToBottom)
when {
minDistance == distanceToLeft -> x = 0f
minDistance == distanceToRight -> x = (screenWidth - width).toFloat()
minDistance == distanceToTop -> y = 0f
minDistance == distanceToBottom -> y = (screenHeight - height).toFloat()
}
}
return super.onTouchEvent(event)
}
}
使用这个自定义 View,你可以轻松地在你的应用中实现随意拖拽和可吸边的悬浮功能,让用户操作更加便捷、高效。
常见问题解答
1. 如何自定义悬浮 View 的外观?
可以通过修改 XML 布局文件中的背景颜色、边框和圆角半径等属性来自定义悬浮 View 的外观。
2. 如何在悬浮 View 中添加自定义控件?
可以在内容区域(FrameLayout)中添加任何自定义控件,例如按钮、文本视图或图像视图。
3. 如何限制悬浮 View 的移动范围?
可以通过在 updatePosition()
方法中添加额外的边界检查来限制悬浮 View 的移动范围。
4. 如何在悬浮 View 消失后保存其位置?
可以通过在 onDetachedFromWindow()
方法中存储悬浮 View 的当前位置,并在重新创建时恢复该位置。
5. 如何处理多个悬浮 View 之间的重叠?
可以通过使用一个全局变量来跟踪所有悬浮 View 的位置,并根据需要调整它们的顺序或透明度。