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方式

我的Android进阶之旅强烈推荐 一种优雅的方式实现RecyclerView条目多类型

如何优雅的处理 Android 重复点击 [建议收藏]

如何优雅的处理 Android 重复点击 [建议收藏]