Android処理ボタン重複クリックイベント

8501 ワード

前言
Androidアプリでは、ページのジャンプ、サーバーのリクエストなど、ボタンのクリックが随所に見られます.ボタンの繰り返しクリックを処理しないと一連の問題になるため、ボタンの複数回クリックを防止することは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:シナリオ1に基づいてパッケージ化し、インタフェースコールバック方式でクリックイベントを処理する
シナリオ3:Rxjava処理を用いて繰り返しクリック
public class RxView {
    public static int intervalTime;

    /**
     *         
     *
     * @param intervalTime
     * @return
     */
    public static void setIntervalTime(int intervalTime) {
        RxView.intervalTime = intervalTime;
    }

    /**
     *       
     *
     * @param target   view
     * @param action    
     */
    public static void setOnClickListeners(final OnRxViewClickListener action, @NonNull View... target) {
        for (View view : target) {
            RxView.onClick(view).throttleFirst(intervalTime == 0 ? 1000 : intervalTime, TimeUnit.MILLISECONDS).subscribe(new Consumer() {
                @Override
                public void accept(@io.reactivex.annotations.NonNull View view) throws Exception {
                    action.onRxViewClick(view);
                }
            });
        }
    }

    /**
     *   onclick     
     *
     * @param view
     * @return
     */
    @SuppressLint("RestrictedApi")
    @CheckResult
    @NonNull
    private static Observable onClick(@NonNull View view) {
        checkNotNull(view, "view == null");
        return Observable.create(new ViewClickOnSubscribe(view));
    }

    /**
     * onclick     
     *   view
     */
    private static class ViewClickOnSubscribe implements ObservableOnSubscribe {
        private View view;

        public ViewClickOnSubscribe(View view) {
            this.view = view;
        }

        @Override
        public void subscribe(@io.reactivex.annotations.NonNull final ObservableEmitter e) throws Exception {
            checkUiThread();

            View.OnClickListener listener = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!e.isDisposed()) {
                        e.onNext(view);
                    }
                }
            };
            view.setOnClickListener(listener);
        }
    }

    /**
     * A one-argument action.         
     *
     * @param  the first argument type
     */
    public interface OnRxViewClickListener {
        /**
         *     
         *
         * @param view
         */
        void onRxViewClick(View view);
    }
}
public class Preconditions {
    public static void checkArgument(boolean assertion, String message) {
        if (!assertion) {
            throw new IllegalArgumentException(message);
        }
    }

    public static  T checkNotNull(T value, String message) {
        if (value == null) {
            throw new NullPointerException(message);
        }
        return value;
    }

    public static void checkUiThread() {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new IllegalStateException(
                    "Must be called from the main thread. Was: " + Thread.currentThread());
        }
    }

    private Preconditions() {
        throw new AssertionError("No instances.");
    }
}

使用方法:
1、クリックイベントが必要なクラスでRxViewを実現する.OnRxViewClickListenerインタフェース
2、クリック時間の設定とクリックイベントの追加
RxView.setIntervalTime(2000);
RxView.setOnClickListeners(this, rx_view);

3、イベント処理はインタフェースの実現方法で処理すればよい
@Override
public void onRxViewClick(View view) {
    switch (view.getId()) {
        case R.id.rx_view:
            LogUtil.e("   ");
            break;
        default:
            break;
    }
}

このシナリオは応答的にボタンクリックを処理し、rxjavaのオペレータを利用して重複クリックを防止し、シナリオ1、シナリオ2よりも優雅である.
思考:以上の3つの案は、いずれも、既存のクリックイベントに大きな侵入性を持っているか、Clickイベントに方法を加える必要があるか、Clickイベント全体を置き換える必要があるか、既存の論理を変更せずにボタンの繰り返しクリックをうまく処理できる方法はありませんか.これが私たちの案4です.
方案四:もっと優雅な処理方式---AOP切面プログラミング
一、AOPをどのように使って重複クリックの問題を解決しますか?
1.Aspectjの導入
AndroidではAOPプログラミングを使用し、一般的にAspectjというライブラリを使用しています.巨人の肩に立って、上海江はすでにAspectjのGradleプラグインをオープンしています.Aspectjを使用するのに便利です.
  • プロジェクトのルートディレクトリの下にあるbuild.gradleでは、依存
  • を追加します.
    dependencies {
       
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
    
      
    }
  • appまたは他のmoduleディレクトリの下にあるbuild.gradleで、次のコードを追加します:
  • apply plugin: 'android-aspectjx'
    
    dependencies {
      implementation 'org.aspectj:aspectjrt:1.8.13'
    }

    注意:aspectjxがmoduleまたはlibraryに依存を導入する場合、appのbuild.gradleも依存を追加する必要があります.moduleまたはlibraryのaop注釈を使用すると有効になりません.(具体的な原因はまだ発見されていない)
    2、カスタム注釈を追加する
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public  @interface AopOnclick {
        /**
         *       
         */
        long value() default 1000;
    }

    3、繰り返しクリック判定ツール類をパッケージする
    public class AopClickUtil {
        /**
         *          
         */
        private static long mLastClickTime;
        /**
         *          ID
         */
        private static int mLastClickViewId;
    
        /**
         *        
         *
         * @param v       
         * @param intervalMillis      (  )
         * @return  true: ,false:  
         */
        public static boolean isFastDoubleClick(View v, long intervalMillis) {
            int viewId = v.getId();
    //        long time = System.currentTimeMillis();
            long time = SystemClock.elapsedRealtime();
            long timeInterval = Math.abs(time - mLastClickTime);
            if (timeInterval < intervalMillis && viewId == mLastClickViewId) {
                return true;
            } else {
                mLastClickTime = time;
                mLastClickViewId = viewId;
                return false;
            }
        }
    }

    4、Aspect AOP処理類の作成
    @Aspect
    public class AopClickAspect {
    
        /**
         *     ,        @AopOnclick     
         *   :  com.freak.aop.AopOnclick     
         *       AopOnclick       
         */
        @Pointcut("execution(@com.freak.httpmanage.aop.AopOnclick * *(..))")
        public void methodAnnotated() {}
    
        /**
         *         ,      
         */
        @Around("methodAnnotated()")
        public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
            //        
            View view = null;
            for (Object arg : joinPoint.getArgs()) {
                if (arg instanceof View) {
                    view = (View) arg;
                    break;
                }
            }
            if (view == null) {
                return;
            }
            //        
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            if (!method.isAnnotationPresent(AopOnclick.class)) {
                return;
            }
           AopOnclick aopOnclick = method.getAnnotation(AopOnclick.class);
            //         
            if (!AopClickUtil.isFastDoubleClick(view, aopOnclick.value())) {
                //       ,     
                joinPoint.proceed();
            }
        }
    }

    二、使用方法
    クリックイベントのメソッドに@AopOnclickコメントを加えるだけで、クリックイベントの繰り返しの問題を処理できます.コードは以下の通りです.
    @AopOnclick(5000)
    public void aop(View view) {
        LogUtil.e("   ");
    }

     
    aop.setOnClickListener(new View.OnClickListener() {
        @AopOnclick
        @Override
        public void onClick(View v) {
            Log.e(TAG, "   ");
        }
    });

    @AopOnclickコメントの後に値を追加します.クリックの間隔を設定します.デフォルトは1000ミリ秒です.
    まとめ:以上の4つの案を比較した後、案4は最も簡単で乱暴で、1つの注釈で問題を解決した.もちろん、処理方法の選択も個人のニーズ次第です.
    参考記事:
    https://github.com/sososeen09/android-blog-demos/tree/master/aop-tech
    https://www.jianshu.com/p/c66f4e3113b3