Android 优雅处理重复点击(建议收藏) 您所在的位置:网站首页 待产包买多少够用 Android 优雅处理重复点击(建议收藏)

Android 优雅处理重复点击(建议收藏)

2024-02-25 17:16| 来源: 网络整理| 查看: 265

一般手机上的 Android App,主要的交互方式是点击。用户在点击后,App 可能做出在页面内更新 UI、新开一个页面或者发起网络请求等操作。Android 系统本身没有对重复点击做处理,如果用户在短时间内多次点击,则可能出现新开多个页面或者重复发起网络请求等问题。因此,需要对重复点击有影响的地方,增加处理重复点击的代码。

之前的处理方式

之前在项目中使用的是 RxJava 的方案,利用第三方库 RxBinding 实现了防止重复点击:

fun View.onSingleClick(interval: Long = 1000L, listener: (View) -> Unit) {     RxView.clicks(this)         .throttleFirst(interval, TimeUnit.MILLISECONDS)         .subscribe({             listener.invoke(this)         }, {             LogUtil.printStackTrace(it)         }) }

但是这样有一个问题,比如使用两个手指同时点击两个不同的按钮,按钮的功能都是新开页面,那么有可能会新开两个页面。因为 Rxjava 这种方式是针对单个控件实现防止重复点击,不是多个控件。

现在的处理方式

现在使用的是时间判断,在时间范围内只响应一次点击,通过将上次单击时间保存到 Activity Window 中的 decorView 里,实现一个 Activity 中所有的 View 共用一个上次单击时间。

fun View.onSingleClick(     interval: Int = SingleClickUtil.singleClickInterval,     isShareSingleClick: Boolean = true,     listener: (View) -> Unit ) {     setOnClickListener {         val target = if (isShareSingleClick) getActivity(this)?.window?.decorView ?: this else this         val millis = target.getTag(R.id.single_click_tag_last_single_click_millis) as? Long ?: 0         if (SystemClock.uptimeMillis() - millis >= interval) {             target.setTag(                 R.id.single_click_tag_last_single_click_millis, SystemClock.uptimeMillis()             )             listener.invoke(this)         }     } } private fun getActivity(view: View): Activity? {     var context = view.context     while (context is ContextWrapper) {         if (context is Activity) {             return context         }         context = context.baseContext     }     return null }

参数 isShareSingleClick 的默认值为 true,表示该控件和同一个 Activity 中其他控件共用一个上次单击时间,也可以手动改成 false,表示该控件自己独享一个上次单击时间。

mBinding.btn1.onSingleClick {     // 处理单次点击 } mBinding.btn2.onSingleClick(interval = 2000, isShareSingleClick = false) {     // 处理单次点击 }其他场景处理重复点击间接设置点击

除了直接在 View 上设置的点击监听外,其他间接设置点击的地方也存在需要处理重复点击的场景,比如说富文本和列表。

为此将判断是否触发单次点击的代码抽离出来,单独作为一个方法:

fun View.onSingleClick(     interval: Int = SingleClickUtil.singleClickInterval,     isShareSingleClick: Boolean = true,     listener: (View) -> Unit ) {     setOnClickListener { determineTriggerSingleClick(interval, isShareSingleClick, listener) } } fun View.determineTriggerSingleClick(     interval: Int = SingleClickUtil.singleClickInterval,     isShareSingleClick: Boolean = true,     listener: (View) -> Unit ) {     ... }

直接在点击监听回调中调用 determineTriggerSingleClick 判断是否触发单次点击。下面拿富文本和列表举例。

富文本

继承 ClickableSpan,在 onClick 回调中判断是否触发单次点击:

inline fun SpannableStringBuilder.onSingleClick(     listener: (View) -> Unit,     isShareSingleClick: Boolean = true,     ... ): SpannableStringBuilder = inSpans(     object : ClickableSpan() {         override fun onClick(widget: View) {             widget.determineTriggerSingleClick(interval, isShareSingleClick, listener)         }         ...     },     builderAction = builderAction )

这样会有一个问题, onClick 回调中的 widget,就是设置富文本的控件,也就是说如果富文本存在多个单次点击的地方, 就算 isShareSingleClick 值为 false,这些单次点击还是会共用设置富文本控件的上次单击时间。

因此,这里需要特殊处理,在 isShareSingleClick 为 false 的时候,创建一个假的 View 来触发单击事件,这样富文本中多个单次点击 isShareSingleClick 为 false 的地方都有一个自己的假的 View 来独享上次单击时间。

class SingleClickableSpan(     ... ) : ClickableSpan() {     private var mFakeView: View? = null     override fun onClick(widget: View) {         if (isShareSingleClick) {             widget         } else {             if (mFakeView == null) {                 mFakeView = View(widget.context)             }             mFakeView!!         }.determineTriggerSingleClick(interval, isShareSingleClick, listener)     }     ... }

在设置富文本的地方,使用设置 onSingleClick 实现单次点击:

mBinding.tvText.movementMethod = LinkMovementMethod.getInstance() mBinding.tvText.highlightColor = Color.TRANSPARENT mBinding.tvText.text = buildSpannedString {     append("normalText")     onSingleClick({         // 处理单次点击     }) {         color(Color.GREEN) { append("clickText") }     } }列表

列表使用 RecyclerView 控件,适配器使用第三方库 BaseRecyclerViewAdapterHelper。

Item 点击:

adapter.setOnItemClickListener { _, view, _ ->     view.determineTriggerSingleClick {         // 处理单次点击     } }

Item Child 点击:

adapter.addChildClickViewIds(R.id.btn1, R.id.btn2) adapter.setOnItemChildClickListener { _, view, _ ->     when (view.id) {         R.id.btn1 -> {             // 处理普通点击         }         R.id.btn2 -> view.determineTriggerSingleClick {             // 处理单次点击         }     } }数据绑定

使用 DataBinding 的时候,有时会在布局文件中直接设置点击事件,于是在 View.onSingleClick 上增加 @BindingAdapte 注解,实现在布局文件中设置单次点击事件,并对代码做出调整,这个时候需要将项目中 listener: (View) -> Unit 替换成 listener: View.OnClickListener。

@BindingAdapter(     *["singleClickInterval", "isShareSingleClick", "onSingleClick"],     requireAll = false ) fun View.onSingleClick(     interval: Int? = SingleClickUtil.singleClickInterval,     isShareSingleClick: Boolean? = true,     listener: View.OnClickListener? = null ) {     if (listener == null) {         return     }     setOnClickListener {         determineTriggerSingleClick(             interval ?: SingleClickUtil.singleClickInterval, isShareSingleClick ?: true, listener         )     } }

在布局文件中设置单次点击:

在代码中处理单次点击:

class YourViewModel : ViewModel() {     fun handleClick() {         // 处理单次点击     } }总结

对于直接在 View 上设置点击的地方,如果需要处理重复点击使用 onSingleClick,不需要处理重复点击则使用原来的 setOnClickListener。

对于间接设置点击的地方,如果需要处理重复点击,则使用 determineTriggerSingleClick 判断是否触发单次点击。

项目地址

https://github.com/TaylorKunZhang/single-click,

原文



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有