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毫秒内的点击进行了过滤。以后开心地写代码的时候再也不用想着异步操作防止用户手抖啦!


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值