欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-24 21:37:52
...

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

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