Android如何优雅的处理重复点击

作者:张坤的笔记 时间:2022-08-11 20:22:35 

目录
  • 之前的处理方式

  • 现在的处理方式

  • 其他场景处理重复点击

    • 间接设置点击

    • 富文本

    • 列表

    • 数据绑定

  • 总结

    • 项目地址

      一般手机上的 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
       )
      }
      }

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


      <androidx.appcompat.widget.AppCompatButton
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/btn"
      app:isShareSingleClick="@{false}"
      app:onSingleClick="@{()->viewModel.handleClick()}"
      app:singleClickInterval="@{2000}" />

      在代码中处理单次点击:


      class YourViewModel : ViewModel() {

      fun handleClick() {
       // 处理单次点击
      }
      }

      总结

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

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

      项目地址

      single-click,觉得用起来很爽的,请不要吝啬你的 Star !

      来源:https://juejin.cn/post/6941746521016631332

      标签:Android,重复,点击
      0
      投稿

      猜你喜欢

    • Android自定义view实现仿抖音点赞效果

      2021-11-04 11:58:19
    • WebSocket实现Web聊天室功能

      2023-11-27 06:10:52
    • Java字符串拼接新方法 StringJoiner用法详解

      2022-06-16 13:02:13
    • Android用viewPager2实现UI界面翻页滚动的效果

      2023-06-08 02:46:43
    • 教你如何用C#制作文字转换成声音程序

      2022-01-15 07:14:40
    • Java获取接口所有实现类的方式详解

      2022-06-11 14:44:27
    • C#中的除法运算符与VB.NET中的除法运算符

      2022-04-01 10:52:56
    • Android编程解析XML文件的方法详解【基于XmlPullParser】

      2022-09-21 22:48:40
    • SpringBoot实现任意位置获取HttpServletRequest对象

      2023-07-07 10:26:40
    • Kotlin select使用方法介绍

      2022-05-28 19:34:27
    • Android中ActionBar以及menu的代码设置样式

      2023-11-24 03:34:33
    • Android获取短信验证码的实现方法

      2023-10-12 03:27:34
    • SpringBoot利用切面注解及反射实现事件监听功能

      2022-09-25 16:55:00
    • c#实现多线程局域网聊天系统

      2022-12-01 23:34:25
    • C#接口(Interface)用法分析

      2022-10-18 10:59:17
    • 软件开发七大过程模型

      2023-12-22 17:06:14
    • Java开发druid数据连接池maven方式简易配置流程示例

      2021-05-26 14:57:23
    • android开发教程之实现listview下拉刷新和上拉刷新效果

      2022-07-29 20:42:42
    • C# WinForm打开PDF文件并在窗体中显示

      2023-11-14 10:47:49
    • java如何对map进行排序详解(map集合的使用)

      2022-12-22 18:26:13
    • asp之家 软件编程 m.aspxhome.com