年关将近大家都回家了,只有我这种天生牛马圣体还在上班
闲着没事干,搞点离谱的东西,用这几天的时间把点击事件进行了一个封装,其实并没有什么卵用,还是上代码吧
```
/**
* @author 浩楠
*
* @date 2025/1/22 10:19
*
* _ _ _ _ ____ _ _ _
* / \ _ __ __| |_ __ ___ (_) __| | / ___|| |_ _ _ __| (_) ___
* / _ \ | '_ \ / _` | '__/ _ | |/ _` | ___ | __| | | |/ _` | |/ _ \
* / ___ | | | | (_| | | | (_) | | (_| | ___) | |_| |_| | (_| | | (_) |
* /_/ __| |_|__,_|_| ___/|_|__,_| |____/ __|__,_|__,_|_|___/
* 描述: TODO 基于flow封装的点击事件
*/
object ViewFlowExtensions {
/**
* 收集多个 View 的事件流
* @param events 可变参数,支持多种 View 事件(点击、长按、防抖点击)
*/
fun LifecycleOwner.collectViewEvents(vararg events: ViewEvent) {
lifecycleScope.launch {
events.forEach { event ->
launch {
when (event) {
is ViewEvent.Click -> event.view.clicks().collect { event.action() }
is ViewEvent.LongClick -> event.view.longClicks().collect { event.action() }
is ViewEvent.ThrottleClick -> event.view.throttleClicks(event.duration)
.collect { event.action() }
is ViewEvent.Touch -> event.view.touchActionsFlow().collect { event.touchHandler(it) }
}
}
}
}
}
/**
* 设置 View 的普通点击事件
* @param action 点击时的操作
*/
fun View.onClick(action: () -> Unit) {
setOnClickListener { action() }
}
/**
* 设置 View 的长按事件
* @param action 长按时的操作
*/
fun View.onLongClick(action: () -> Unit) {
setOnLongClickListener {
action()
true
}
}
/**
* 创建一个 Flow 用于监听点击事件
* @return Flow<Unit> 点击事件流
*/
fun View.clicks(): Flow<Unit> = callbackFlow {
val listener = View.OnClickListener { trySend(Unit).isSuccess }
setOnClickListener(listener)
awaitClose { setOnClickListener(null) }
}.shareInLifecycle()
/**
* 创建一个 Flow 用于监听防抖点击事件
* @param duration 防抖间隔时间(单位:毫秒),默认为 1000ms
* @return Flow<Unit> 防抖点击事件流
*/
fun View.throttleClicks(duration: Long = 1000L): Flow<Unit> =
clicks().throttleFirst(duration)
/**
* 创建一个 Flow 用于监听长按事件
* @return Flow<Unit> 长按事件流
*/
fun View.longClicks(): Flow<Unit> = callbackFlow {
val listener = View.OnLongClickListener {
trySend(Unit).isSuccess
true
}
setOnLongClickListener(listener)
awaitClose { setOnLongClickListener(null) }
}.shareInLifecycle()
/**
* 设置 View 的四方向点击事件(支持扩展触摸区域)
* @param extraTouchArea 额外扩展的触摸区域大小
* @param onStart 向左点击时触发的操作
* @param onTop 向上点击时触发的操作
* @param onEnd 向右点击时触发的操作
* @param onBottom 向下点击时触发的操作
*/
fun View.onDirectionalClick(
extraTouchArea: Int = 0,
onStart: (() -> Unit)? = null,
onTop: (() -> Unit)? = null,
onEnd: (() -> Unit)? = null,
onBottom: (() -> Unit)? = null
) {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
val viewBounds = calculateViewBounds(extraTouchArea)
when {
onStart != null && event.x <= viewBounds.left -> {
onStart()
return@setOnTouchListener true
}
onTop != null && event.y <= viewBounds.top -> {
onTop()
return@setOnTouchListener true
}
onEnd != null && event.x >= viewBounds.right -> {
onEnd()
return@setOnTouchListener true
}
onBottom != null && event.y >= viewBounds.bottom -> {
onBottom()
return@setOnTouchListener true
}
}
}
false
}
}
/**
* 创建一个 Flow 用于监听四方向点击事件
* @param extraTouchArea 额外扩展的触摸区域大小
* @return Flow<DirectionalPosition> 四方向点击事件流
*/
fun View.directionalClicks(extraTouchArea: Int = 0): Flow<DirectionalPosition> = callbackFlow {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
val viewBounds = calculateViewBounds(extraTouchArea)
when {
event.x <= viewBounds.left -> trySend(DirectionalPosition.START).isSuccess
event.y <= viewBounds.top -> trySend(DirectionalPosition.TOP).isSuccess
event.x >= viewBounds.right -> trySend(DirectionalPosition.END).isSuccess
event.y >= viewBounds.bottom -> trySend(DirectionalPosition.BOTTOM).isSuccess
}
}
false
}
awaitClose { setOnTouchListener(null) }
}.shareInLifecycle()
/**
* 创建一个 Flow,用于监听 View 的所有 Touch 事件
* @return Flow<MotionEvent> 返回 MotionEvent 的流
*/
fun View.touchActionsFlow(): Flow<MotionEvent> = callbackFlow {
val listener = View.OnTouchListener { _, event ->
trySend(event).isSuccess // 将 MotionEvent 发送到流中
true // 消费事件
}
setOnTouchListener(listener) // 设置监听器
awaitClose { setOnTouchListener(null) } // 在流关闭时移除监听器
}.shareInLifecycle() // 绑定生命周期,防止内存泄漏
/**
* 创建一个 Flow,用于监听指定类型的 Touch 事件
* @param actions 可变参数,指定监听的 MotionEvent 类型(如 ACTION_DOWN, ACTION_UP 等)
* @return Flow<MotionEvent> 过滤后的 Touch 事件流
*/
fun View.filteredTouchFlow(vararg actions: Int): Flow<MotionEvent> = touchActionsFlow()
.filter { it.action in actions }
/**
* 创建一个 Flow,用于监听常用的 Touch 操作(如 ACTION_DOWN, ACTION_UP, ACTION_CANCEL)
* @return Flow<TouchAction> 返回封装的 TouchAction 类型事件流
*/
fun View.touchEventsFlow(): Flow<TouchAction> = callbackFlow {
val listener = View.OnTouchListener { _, event ->
val action = when (event.action) {
MotionEvent.ACTION_DOWN -> TouchAction.Down
MotionEvent.ACTION_UP -> TouchAction.Up
MotionEvent.ACTION_CANCEL -> TouchAction.Cancel
else -> null
}
action?.let { trySend(it).isSuccess }
true // 消费事件
}
setOnTouchListener(listener)
awaitClose { setOnTouchListener(null) }
}.shareInLifecycle()
/**
* 遍历 ViewGroup 中的所有子 View 并执行操作
* @param action 对每个子 View 执行的操作
*/
private fun ViewGroup.forEachChild(action: (View) -> Unit) {
for (i in 0 until childCount) {
action(getChildAt(i))
}
}
/**
* 创建一个 ThrottleFirst 操作符
* 用于过滤频繁的事件
* @param duration 防抖间隔时间(单位:毫秒)
* @return Flow<T> 限制频率后的事件流
*/
private fun <T> Flow<T>.throttleFirst(duration: Long): Flow<T> = flow {
var lastTime = 0L
collect { value ->
val currentTime = System.currentTimeMillis()
if (currentTime - lastTime >= duration) {
emit(value)
lastTime = currentTime
}
}
}.flowOn(Dispatchers.Default)
/**
* 将 Flow 与生命周期绑定,避免内存泄漏
* @return Flow<T> 生命周期绑定的事件流
*/
private fun <T> Flow<T>.shareInLifecycle(): Flow<T> = shareIn(
scope = CoroutineScope(Dispatchers.Main.immediate),
started = SharingStarted.WhileSubscribed(5000L),
replay = 0
)
/**
* 计算扩展触摸区域的边界
* @param extraTouchArea 额外扩展的触摸区域大小
* @return Rect 包含扩展边界的矩形
*/
private fun View.calculateViewBounds(extraTouchArea: Int): Rect {
val rect = Rect()
getHitRect(rect)
rect.left -= extraTouchArea
rect.top -= extraTouchArea
rect.right += extraTouchArea
rect.bottom += extraTouchArea
return rect
}
/**
* 四方向点击事件的枚举
*/
enum class DirectionalPosition {
START, TOP, END, BOTTOM
}
/**
* 定义封装的 TouchAction 类型
*/
sealed class TouchAction {
object Down : TouchAction() // 按下
object Up : TouchAction() // 松开
object Cancel : TouchAction() // 取消
}
/**
* 定义不同类型的 View 事件
*/
sealed class ViewEvent {
data class Click(val view: View, val action: () -> Unit) : ViewEvent() // 普通点击事件
data class LongClick(val view: View, val action: () -> Unit) : ViewEvent() // 长按事件
data class ThrottleClick(
val view: View,
val duration: Long = 1000L,
val action: () -> Unit
) : ViewEvent() // 防抖点击事件,默认间隔为 1000ms
data class Touch(val view: View, val touchHandler: (MotionEvent) -> Unit) : ViewEvent() // Touch 事件
}
}
具体的代码呢,就是这样,对比呢就是这样,一般的app都用不到,我也是写着玩,大佬勿喷
| 特性 | Flow 实现的点击事件 | 普通实现的点击事件 |
|---|---|---|
| 实现复杂度 | 较高,需要引入 Flow 和协程,代码量较多。 | 简单,直接使用 setOnClickListener,代码实现快捷。 |
| 事件监听灵活性 | 高,可以结合各种操作符(如防抖、合并流等)处理复杂交互逻辑。 | 低,只能处理简单的点击逻辑。 |
| 防抖和节流功能 | 内置支持,通过 throttleFirst 等操作符轻松实现防抖和节流。 | 需要额外编写逻辑才能实现防抖功能,增加代码量。 |
| 事件组合能力 | 强,可以通过 merge 合并多个点击事件流到统一逻辑中处理。 | 无法直接组合事件,需要额外手动编写合并逻辑。 |
| 生命周期感知 | 与 Lifecycle 绑定,监听器自动管理,避免内存泄漏。 | 需要手动移除监听器,稍有疏忽容易导致内存泄漏。 |
| 可读性和维护性 | 高,复杂交互逻辑可以以链式操作清晰描述,代码结构简洁明了。 | 较低,复杂逻辑需要嵌套多段代码,结构不清晰。 |
| 性能 | 轻微运行时开销(Flow 的调度和事件处理机制带来的调度消耗)。 | 性能高,直接触发监听器,无额外开销。 |
| 多事件支持 | 强,可以同时监听点击、长按、触摸等多种事件,支持事件组合处理。 | 弱,仅能处理单一事件类型,多事件需要额外实现。 |
| 适用场景 | 适用于复杂交互场景(如多事件合并处理、防抖点击、生命周期管理等)。 | 适用于简单交互场景,如单一点击或快速响应需求。 |