我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

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

以上就是Android如何优雅的处理重复点击的详细内容,更多关于Android 处理重复点击的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JavaScript如何优雅的处理async/await

这篇文章给大家分享的是有关JavaScript如何优雅的处理async/await的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。优雅的处理 async/await使用方法:无需每次使用 async/await 都
2023-06-27

Android开发如何解决安卓重复点击问题

这篇文章主要介绍“Android开发如何解决安卓重复点击问题”,在日常操作中,相信很多人在Android开发如何解决安卓重复点击问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Android开发如何解决安卓
2023-07-02

Java:如何更优雅的处理空值?

在笔者几年的开发经验中,经常看到项目中存在到处空值判断的情况,这些判断,会让人觉得摸不着头绪,它的出现很有可能和当前的业务逻辑并没有关系。但它会让你很头疼。有时候,更可怕的是系统因为这些空值的情况,会抛出空指针异常,导致业务系统发生问题。此
2023-06-03

HTML 标签嵌套的挑战:如何优雅地处理复杂结构

HTML 标签嵌套在构建复杂网页结构时不可避免,但过多的嵌套会对可读性和可维护性造成负面影响。本文探讨了优雅处理复杂结构的挑战,并提供了一些有效的解决方案。
HTML 标签嵌套的挑战:如何优雅地处理复杂结构
2024-02-03

如何进行C++代码的优雅异常处理?

如何进行C++代码的优雅异常处理?在编程过程中,异常处理是一个重要的方面。异常处理可以帮助我们在代码执行过程中处理意外情况,保证程序的稳定性和可靠性。在C++中,异常处理的机制能有效地捕获和处理错误,从而帮助我们优雅地处理异常情况。本文将介
如何进行C++代码的优雅异常处理?
2023-11-02

如何优雅地处理golang函数中的错误

在 golang 中使用 customerror 类型可以优雅地处理函数中的错误,为您提供:自定义错误消息以提供更多详细信息。错误分类以将错误分组到类别中。错误堆栈跟踪以帮助追踪错误的根源。如何通过 Golang 的 CustomError
如何优雅地处理golang函数中的错误
2024-04-24

ASP 错误处理的艺术:如何优雅地处理意外情况

ASP 错误处理的艺术在于能够正确而优雅地处理意外情况,包括避免错误的发生、检测错误的发生、以及在错误发生后进行合理的处理。
ASP 错误处理的艺术:如何优雅地处理意外情况
2024-02-10

Vue中如何优雅地处理组件的销毁与清理?

优雅地处理Vue组件的销毁与清理至关重要。利用beforeDestroy生命周期钩子执行清理操作,并使用mounted钩子保存资源。destroyed钩子用于释放资源,v-on:beforeDestroy指令和Vuex可简化清理。避免使用setTimeout和setInterval,考虑第三方库或GC标记。单元测试验证组件销毁和清理行为。遵循最佳实践,例如响应式数据和显式释放资源,以防止内存泄漏。
Vue中如何优雅地处理组件的销毁与清理?
2024-04-02

Vue中如何优雅地处理组件的依赖注入?

Vue提供多种依赖注入方式,包括服务注入、依赖性注入容器、依赖关系传值和插件系统。最佳实践取决于应用程序需求:可访问性:服务注入提供全局访问,而其他方式更适用于特定组件。测试性:容器和传值通过模拟依赖关系提高了测试性。模块化:容器提供了明确的依赖关系管理系统。性能:服务注入最快,而容器和传值可能带来开销。通过选择最适合您用例的技术,您可以优雅地处理Vue中的组件依赖注入,提升代码的可维护性、可测试性和性能。
Vue中如何优雅地处理组件的依赖注入?
2024-04-02

Vue中如何优雅地处理组件间的通信问题?

Vue中组件间通信指南:父子组件:Props(单向传递数据)和Events(子组件触发事件)兄弟组件:EventBus(全局事件总线)和Vuex(集中式状态管理)非父子组件:provide/inject(注入数据)、插槽(组件内嵌)插槽:允许子组件内容注入父组件,作用域插槽可访问父组件数据和方法组合API:useEmit()、useProvide()等,提供简洁的通信方式最佳实践:保持组件解耦、明确事件名称、使用数据流图、避免过度通信、使用Vuedevtools调试
Vue中如何优雅地处理组件间的通信问题?
2024-04-02

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录