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 处理重复点击的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android 快速实现防止网络重复请求&按钮重复点击的方法

    在日常开发过程中,偶尔会出现一些极端问题.比如 网络重复请求,很难过滤 请求的问题. 下面一段代码,可以解决这个重复请求的问题. 下面上一段代码: private long lastClick; // 防止网络重新请求 if (System.currentTimeMillis() - lastClick <= 1000) { return; } lastClick = System.currentTimeMillis(); 以上这篇Android 快速实现防止网络重复请求&按钮重复点击的方法

  • Android优雅地处理按钮重复点击的几种方法

    App中,有很大一部分场景是点击按钮,向服务端提交数据,由于网络请求需要时间,用户很可能会多次点击,造成数据重复提交,造成各种莫名其妙的问题. 因此,防止按钮多次点击,是Android开发中一个很重要的技术手段. 以前的处理方式 网上查找到的,或者你可能会想到的方法大概有这些: 1.每个按钮点击事件中,记录点击时间,判断是否超过点击时间间隔 private long mLastClickTime = 0; public static final long TIME_INTERVAL = 1000

  • Android中如何优雅的处理重复点击实例代码

    问题 有时候有些操作是防止用户在一次响应结束中再响应下一个.但有些测试用户就要猛点,狂点.像这种恶意就要进行防止. 比如在客户端中,一些按钮一般是需要避免重复点击的,比如:购买丶支付丶确定丶提交丶点赞丶收藏等等场景,这些场景短时间内的重复点击会引发一些问题. 下面话不多说了,来一起看看详细的介绍吧 以前的处理方式 可能是采用手动记录最后的点击时间,再通过计算时间间隔来判断是否重复点击 private long mLastClickTime = 0; public static final int

  • Android之有效防止按钮多次重复点击的方法(必看篇)

    为了防止测试妹子或者用户频繁点击某个按钮,导致程序在短时间内进行多次数据提交or数据处理,那到时候就比较坑了~ 那么如何有效避免这种情况的发生呢? 我的想法是,判断用户点击按钮间隔时间,如果间隔时间太短,则认为是无效操作,否则进行相关业务处理 首先将这块提取为工具类(方便接下来的调用),现在就起名为:ButtonUtils public class ButtonUtils { private static long lastClickTime = 0; private static long D

  • Android防止按钮重复点击示例代码

    本文中我将介绍一下我自己封装的一个小的工具类库:按钮点击事件类库. 作用:该类库可以防止按钮重复点击,可以判断网络状态,可以判断用户登录状态,以及自定义验证条件等等. 说明:其实现的核心原理就是通过自定义实现自身的OnClickListener类,并重写其中的onClick方法,在onClick方法中执行相应的判断逻辑之后回调我们自定义的抽象方法. 具体效果如下图所示: 使用方式 屏蔽多次点击事件 /** * 测试快速点击事件 */ fastButton.setOnClickListener(n

  • Android 防止多次重复点击的三种方法的示例

    在项目中经常会遇到对按钮.自定义控件的 Item 等防止多次重复的点击的问题,下面做一个小结. 方法1:使用 RxJava 的 throttleFirst() 具体代码如下: /** * 防止重复点击 * * @param target 目标view * @param listener 监听器 */ public static void preventRepeatedClick(final View target, final View.OnClickListener listener) { R

  • Android中AOP的应用实践之过滤重复点击

    前言 大家对AOP应该都不陌生, 就算没有用过也肯定听说过,切面编程一直是一个热点的话题,AOP即Aspect Oriented Programming的缩写,习惯称为切面编程;与OOP(面向对象编程)万物模块化的思想不同,AOP则是将涉及到众多模块的某一类问题进行统一管理,AOP的优点是将业务逻辑与系统化功能高度解耦,让我们在开发过程中可以只专注于业务逻辑,其他一些系统化功能(如路由.日志.权限控制.拦截器.埋点.事件防抖等)则由AOP统一处理; AspectJ简介 AOP是一种编程思想,或者

  • 实例详解Android解决按钮重复点击问题

    为了防止用户或者测试MM疯狂的点击某个button,写个方法防止按钮连续点击.具体实例代码如下所示: public class BaseActivity extends Activity { protected boolean isDestroy; //防止重复点击设置的标志,涉及到点击打开其他Activity时,将该标志设置为false,在onResume事件中设置为true private boolean clickable=true; @Override protected void on

  • Android开发教程之如何屏蔽View的重复点击

    前言 android 防止重复点击是一个非常常见的需求,每个人都有各自的点击事件的处理习惯,有的喜欢使用匿名内部类,有的activity.fragment.自定义View等继承点击事件然后在onClick()方法中根据id用switch实现各自View的点击事件. 在开发中我们经常需要这样的需求,比如一个验证码发送按钮,我们只想让它响应500毫秒中的第一次点击事件,该如何处理呢?你可能会说这个简单,在点击事件中获取当前时间与上次的比较下,如果小于500毫秒就return掉.是的,这样可以解决,但

  • Android 处理 View 重复点击的多种方法

    1.抽象类 //实现 View.OnClickListener 接口 public abstract class OnThrottleClickListener implements View.OnClickListener { private static final String TAG = "OnThrottleClickListener"; private static final int SKIP_DURATION = 300;//milliseconds private l

随机推荐