1 行代码,搞定按钮重复点击难题

App中,有很大一部分场景是点击按钮,向服务端提交数据,由于网络请求需要时间,用户很可能会多次点击,造成数据重复提交,造成各种莫名其妙的问题。

同时如果你是hmi开发,在很多时候也需要此项操作。

因此,防止按钮多次点击,是Android开发中一个很重要的技术手段。

以前的处理方式

网上查找到的,或者你可能会想到的方法大概有这些:

1.每个按钮点击事件中,记录点击时间,判断是否超过点击时间间隔

    private long mLastClickTime = 0;
    public static final long TIME_INTERVAL = 1000L;
    private Button btTest;
    private void initView() {
        btTest = findViewById(R.id.bt_test);
        btTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                long nowTime = System.currentTimeMillis();
                if (nowTime - mLastClickTime > TIME_INTERVAL) {
                    // do something
                    mLastClickTime = nowTime;
                } else {
                    Toast.makeText(MainActivity.this, "不要重复点击", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

这种方式,每个点击事件都需要写一个时间判断,重复代码很多。

2.封装一个点击事件,处理点击间隔判断

    public abstract class CustomClickListener implements View.OnClickListener {
        private long mLastClickTime;
        private long timeInterval = 1000L;
    
        public CustomClickListener() {
    
        }
    
        public CustomClickListener(long interval) {
            this.timeInterval = interval;
        }
    
        @Override
        public void onClick(View v) {
            long nowTime = System.currentTimeMillis();
            if (nowTime - mLastClickTime > timeInterval) {
                // 单次点击事件
                onSingleClick();
                mLastClickTime = nowTime;
            } else {
                // 快速点击事件
                onFastClick();
            }
        }
    
        protected abstract void onSingleClick();
        protected abstract void onFastClick();
    }

使用:

    btTest.setOnClickListener(new CustomClickListener() {
        @Override
        protected void onSingleClick() {
            Log.d("xxx", "onSingleClick");
        }
    
        @Override
        protected void onFastClick() {
            Log.d("xxx", "onFastClick");
        }
    });

相比于第一种方式,这种方法将重复点击的判断封装在CustomClickListener内部,外部无需处理时间判断,只需要实现点击方法即可。

3.利用RxAndroid处理重复点击

    RxView.clicks(view)
        .throttleFirst(1, TimeUnit.SECONDS)
        .subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object o) throws Exception {
                // do something
            }
         });

响应式地处理按钮点击,利用rxjava的操作符,来防止重复点击,相较于第1,2方案来说,此方法更为优雅一些。

偷懒的方式

作为程序员,我们要学会偷懒,能少写的绝不多写,因此我封装成了一个工具类,同时考虑到后面有更好的实现方式,在尽可能小的情况下改动代码;或者每个按钮的点击间隔不一样呢?

1.首先定义一个接口

    public interface ViewClick extends View.OnClickListener {
    
        void throttle(long skipDuration, TimeUnit timeUnit);
    
    }

2.Android原生的实现方式

    public class ViewClickImp implements ViewClick {
    
        private View mView;
        private View.OnClickListener mOnClickListener;
        private long mOldTime;
        private long mDelayMilliseconds = 1000;
    
        public ViewClickImp(View view, View.OnClickListener onClickListener) {
            RxUtil.requestNoNull(view, "view in null");
            RxUtil.requestNoNull(onClickListener, "onClickListener in null");
            mView = view;
            mOnClickListener = onClickListener;
            mView.setOnClickListener(this);
            LogUtil.logD("ViewClick ViewClickImp: ");
        }
    
        @Override
        public void onClick(final View v) {
            // 考虑到用户手动修改系统时间,所以要使用以下方式
            long nowTime = SystemClock.elapsedRealtime();
            long intervalTime = nowTime - mOldTime;
            if (mOldTime == 0 || intervalTime >= mDelayMilliseconds) {
                mOldTime = nowTime;
                mOnClickListener.onClick(v);
            }
        }
    
        @Override
        public void throttle(long skipDuration, TimeUnit timeUnit) {
            if (skipDuration < 0) {
                skipDuration = 0;
            }
            RxUtil.requestNoNull(timeUnit, "timeUnit in null");
            mDelayMilliseconds = timeUnit.toMillis(skipDuration);
        }
    }

需要注意:
System.currentTimeMillis()受系统时间的影响,因此在这里要用SystemClock.elapsedRealtime()

3.RxJava的实现方式

    public class RxViewClickImp implements ViewClick {
    
        private View mView;
        private View.OnClickListener mOnClickListener;
        private long mSkipDuration;
        private TimeUnit mTimeUnit = TimeUnit.MILLISECONDS;
        private ObservableEmitter mObservableEmitter;
        private Observable mObservable;
    
        public RxViewClickImp(View view, View.OnClickListener onClickListener) {
            RxUtil.requestNoNull(view, "view in null");
            RxUtil.requestNoNull(onClickListener, "onClickListener in null");
            mView = view;
            mOnClickListener = onClickListener;
            mView.setOnClickListener(this);
            LogUtil.logD("ViewClick RxViewClickImp: ");
        }
    
        @Override
        public void onClick(final View v) {
            if (mObservable == null) {
                mObservable = Observable.create(new ObservableOnSubscribe<View>() {
    
                    @Override
                    public void subscribe(final ObservableEmitter<View> emitter) throws Exception {
                        mObservableEmitter = emitter;
                    }
                });
                throttleFirst(mObservable);
            }
            mObservableEmitter.onNext(mView);
        }
    
        @Override
        public void throttle(long skipDuration, TimeUnit timeUnit) {
            mSkipDuration = skipDuration;
            mTimeUnit = timeUnit;
        }
    
        private void throttleFirst(Observable observable) {
            observable.throttleFirst(mSkipDuration, mTimeUnit)
                    .subscribe(new Consumer<View>() {
                        @Override
                        public void accept(View view) throws Exception {
                            mOnClickListener.onClick(view);
                        }
                    });
        }
    
    }

需要注意:在RxViewClickImp对象内只能有一个Observable和ObservableEmitter,不然你会发现设置是无效的

4.ViewClickUtil的实现

1.点击事件

public void clicks(View.OnClickListener onClickListener, View view) {
    switch (mType) {
        case RX_VIEW:
            mViewClick = new RxViewClickImp(view, onClickListener);
            break;
        case VIEW:
            mViewClick = new ViewClickImp(view, onClickListener);
            break;
    }
    mViewClick.throttle(mSkipDuration, mTimeUnit);
}

public void clicks(View.OnClickListener onClickListener, View... views) {
    for (View view : views) {
        clicks(onClickListener, view);
    }
}

2.build

     private ViewClickUtil(Builder builder) {
            mSkipDuration = builder.mSkipDuration;
            mTimeUnit = builder.mTimeUnit;
            mType = builder.mType;
        }
    
      public static class Builder {
    
            private long mSkipDuration;
            private TimeUnit mTimeUnit;
            private Type mType = Type.RX_VIEW;
    
            public Builder setSkipDuration(long mSkipDuration) {
                this.mSkipDuration = mSkipDuration;
                return this;
            }
    
            public Builder setTimeUnit(TimeUnit mTimeUnit) {
                this.mTimeUnit = mTimeUnit;
                return this;
            }
    
            public Builder setType(Type type) {
                this.mType = type;
                return this;
            }
    
            public ViewClickUtil build() {
                return new ViewClickUtil(this);
            }
    
        }

3.定义一个枚举

     public enum Type {
            RX_VIEW, VIEW
        }

4.如何使用

       @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
            new ViewClickUtil.Builder()
                    .setSkipDuration(500)
                    .setTimeUnit(TimeUnit.MILLISECONDS)
                    .setType(ViewClickUtil.Type.RX_VIEW)
                    .build().clicks(this, click1, click2, skip);
        }
    
        long oldTime = SystemClock.elapsedRealtime();
    
        @Override
        public void onClick(View v) {
            long nowTime = SystemClock.elapsedRealtime();
            switch (v.getId()) {
                case R.id.click1:
                    LogUtil.logD("click1: " + (nowTime - oldTime));
                    break;
                case R.id.click2:
                    LogUtil.logD("click2: " + (nowTime - oldTime));
                    break;
                case R.id.skip:
                    startActivity(new Intent(this, SkipActivity.class));
                    break;
            }
            oldTime=  nowTime;
        }

需要注意的是:view的setOnClickListener对象是ViewClick,同时在ViewClick的oclick()方法里面调用传入的View.OnClickListener的onClick()方法
效果图
效果图
这只是楼主的实现方式,大家如果有其他实现方式的可以在评论区留言,如果有要代码的,请点击关注,谢谢!!!

免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总可以加:936332305 / 链接:点击链接加入【安卓开发架构】:https://jq.qq.com/?_wv=1027&k=515xp64

在这里插入图片描述

你可能感兴趣的:(技术文)