Android 一种优雅的方式避免用户快点击(Databinding)
Posted 周文凯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 一种优雅的方式避免用户快点击(Databinding)相关的知识,希望对你有一定的参考价值。
在android开发中比较常见的交互是响应用户的点击进行下一步的操作,作为老司机的我们都知道,即便不是用户故意快速点击也可能会出现多次响应的情况,尤其需要等待异步处理结果时,这是不能忍受的。突出体现是向服务端连续提交了两次请求。
典型方案
利用时间差
private int lastId;
private long lastTimeStamp;
public boolean isValidClick(View view)
long time = System.currentTimeMillis();
boolean valid = view.getId() != lastId || time - lastTimeStamp > 500;
lastId = view.getId();
lastTimeStamp = time;
return valid;
使用RxJava
public void bindClick(final View target, final View.OnClickListener listener)
RxView
.clicks(target)
.throttleFirst(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribe(new Action1<Void>()
@Override
public void call(Void aVoid)
listener.onClick(target);
);
优雅方案
简单的Demo
XML布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="view"
type="com.baidu.databingtest.MainActivity" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.baidu.databingtest.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@view::onButtonClick"
android:text="Click" />
</android.support.constraint.ConstraintLayout>
</layout>
Code
public class MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.setView(this);
public void onButtonClick(View view)
Toast.makeText(this, "onButtonClick", Toast.LENGTH_SHORT).show();
以上就是一个最简单的使用databinding响应用户点击示例。
分析
对生成的ActivityMainBing进行分析,找到对应绑定点击的代码:
@Override
protected void executeBindings()
long dirtyFlags = 0;
synchronized(this)
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
com.baidu.databingtest.MainActivity view = mView;
android.view.View.OnClickListener viewOnButtonClickAndroidViewViewOnClickListener = null;
if ((dirtyFlags & 0x3L) != 0)
if (view != null)
// read view::onButtonClick
viewOnButtonClickAndroidViewViewOnClickListener = (((mViewOnButtonClickAndroidViewViewOnClickListener == null) ? (mViewOnButtonClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mViewOnButtonClickAndroidViewViewOnClickListener).setValue(view));
// batch finished
if ((dirtyFlags & 0x3L) != 0)
// api target 1
this.mboundView1.setOnClickListener(viewOnButtonClickAndroidViewViewOnClickListener);
可以看到核心的代码是
this.mboundView1.setOnClickListener(viewOnButtonClickAndroidViewViewOnClickListener);
为什么在XML中使用的是
android:onClick="@view::onButtonClick"
到自动生成的代码中是这样的呢?简单的说一下,后续可以写博客详细说明。
在build.gradle添加如下启动使用dataBinding时,会自动引入三个库。
android
// ... ...
dataBinding
enabled = true
compile 'com.android.databinding:library:1.3.3'
compile 'com.android.databinding:adapters:1.3.3'
provided 'com.android.databinding:compiler:3.0.1'
databinding adapters中定义了常用的binding映射,在ViewBindingAdapter类中,将android:onClick映射到了setOnClickListener方法。
@BindingMethods(
// ... ...
@BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"),
// ... ...
)
public class ViewBindingAdapter
// ... ...
既然databinding提供了BindingAdapter,来进行定义xml与View的交互。那么是不是可以采用这种方式呢?
定义ViewBindingAdapter
public class ViewBindingAdapter
private static final String TAG = "ViewBindingAdapter";
@BindingAdapter("android:onClick")
public static void setOnClick(View view, final View.OnClickListener clickListener)
Log.d(TAG, "setOnClick: ");
view.setOnClickListener(clickListener);
再编译一次,自动生成的ActivityMainBing对应绑定点击的代码
@Override
protected void executeBindings()
long dirtyFlags = 0;
synchronized(this)
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
com.baidu.databingtest.MainActivity view = mView;
android.view.View.OnClickListener viewOnButtonClickAndroidViewViewOnClickListener = null;
if ((dirtyFlags & 0x3L) != 0)
if (view != null)
// read view::onButtonClick
viewOnButtonClickAndroidViewViewOnClickListener = (((mViewOnButtonClickAndroidViewViewOnClickListener == null) ? (mViewOnButtonClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mViewOnButtonClickAndroidViewViewOnClickListener).setValue(view));
// batch finished
if ((dirtyFlags & 0x3L) != 0)
// api target 1
com.baidu.databingtest.ViewBindingAdapter.setOnClick(this.mboundView1, viewOnButtonClickAndroidViewViewOnClickListener);
核心代码
com.baidu.databingtest.ViewBindingAdapter.setOnClick(this.mboundView1, viewOnButtonClickAndroidViewViewOnClickListener);
可以看到在使用时无需任何更改,调用的已经是我们定义的方法,那就可以在这里去避免用户的手抖。
最终设计
public class ViewBindingAdapter
@BindingAdapter("android:onClick", "android:clickable")
public static void setOnClick(View view, View.OnClickListener clickListener,
boolean clickable)
setOnClick(view, clickListener);
view.setClickable(clickable);
@BindingAdapter("android:onClick")
public static void setOnClick(View view, final View.OnClickListener clickListener)
final long[] mHits = new long[2];
view.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1);
mHits[mHits.length - 1] = SystemClock.uptimeMillis();
if (mHits[0] < (SystemClock.uptimeMillis() - 500))
clickListener.onClick(v);
);
OK,这样也算是面向切面编程了,我们拦截了所有XML中定义的点击请求,并对500毫秒内的点击进行了过滤。以后开心地写代码的时候再也不用想着异步操作防止用户手抖啦!
以上是关于Android 一种优雅的方式避免用户快点击(Databinding)的主要内容,如果未能解决你的问题,请参考以下文章
Android 基于Jetpack LiveData实现消息总线
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式
Android自定义View实战之仿百度加载动画,一种优雅的Loading方式