敞开成长之旅!这是我参加「日新计划 2 月更文应战」的第 4 天,点击检查活动详情

前言

其实快速点击是个很优点理的问题,可是如何高雅的去处理确是一个难题,本文主要是记录一些本人经过处理快速点击的进程中脑海里浮现的一些对这个问题的沉思。

1. AOP

能够经过AOP来处理这个问题,并且AOP处理的办法也很高雅,在开源上也应该是能找到对应的老练结构。

AOP来处理这类问题其实是近些年一个比较好的思路,包括比方像数据打点,经过AOP去处理,也能得到一个比较高雅的作用。牛逼的人甚至能够不必他人写的结构,自己去封装就行,我由于对这个技能栈不熟,这儿就不献丑了。
总归,假如你想快速又简略的处理这种问题,AOP是一个很好的方案

2. kotlin

运用kotlin的朋友有福了,kotlin中有个概念是扩展函数,运用扩展函数去封装放快速点击的操作逻辑,也能很快的完成这个作用。它的优点便是杰出两个字“便利”

那是不是我用java,不必kotlin就完成不了kotlin这个扩展函数的作用?当然不是了。这让我想到一件事,我也有去看这类问题的文章,看看有没有哪个大神有比较好的思路,然后我注意到有人就说用扩展函数就行,不必这么费事。

OK,那扩展函数是什么?它的原理是什么?不便是静态类去套一层吗?那用java当然能完成,为什么他人用java去封装这套逻辑便是费事呢?代码不都是相同,只不过kotlin帮你做了罢了。所以我觉得kotlin的扩展函数作用是便利,但从全体的处理思路上看,缺少点高雅。

3. 流

简略来说也有许多人用了Rxjava或许kotlin的flow去完成,像这种完成也便是能便利罢了,在底层上并没有什么实质性的突破,所以就不多说了,说白了便是和上面相同。

4. 经过阻拦

由于上面现已说了kt的状况,所以接下来的相关代码都会用java来完成。
经过阻拦来到达避免快速点击的作用,而阻拦我想到有2种办法,第一种是阻拦事情,便是基于事情分发机制去完成,第二种是阻拦办法。
相对而言,其实我觉得阻拦办法会愈加安全,举个场景,假如你有个页面,然后页面正在到核算,到核算完之后会显现一个按钮,点击后弹出一个对话框。然后过了许久,改需求了,改成到核算完之后主动弹出对话框。可是你之前的点击按钮弹出对话框的操作还需求保存。那就会有或许由于某些操作导致到核算完的一瞬间先显现按钮,这时你以迅雷不及掩耳的速度点它,那就弹出两次对话框。

(1)阻拦事情

其实便是给事情加个判别,判别两次点击的时刻假如在某个规模就不触发,这或许是大部分人会用的办法。

正常状况下咱们是无法去侵略事情分发机制的,只能运用它供给的办法去操作,比方咱们没办法在外部影响dispatchTouchEvent这些办法。当然不正常的状况下也许能够,你能够测验往hook的方向去考虑能不能完成,我这边就不考虑这种状况了。

public class FastClickHelper {
    private static long beforeTime = 0;
    private static Map<View, View.OnClickListener> map = new HashMap<>();
    public static void setOnClickListener(View view, View.OnClickListener onClickListener) {
        map.put(view, onClickListener);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                long clickTime = SystemClock.elapsedRealtime();
                if (beforeTime != 0 && clickTime - beforeTime < 1000) {
                    return;
                }
                beforeTime = clickTime;
                View.OnClickListener relListener = map.get(v);
                if (relListener != null) {
                    relListener.onClick(v);
                }
            }
        });
    }
}

简略来写便是这样,其实这个就和上面说的kt的扩展函数差不多。调用的时候就

FastClickHelper.setOnClickListener(view, this);

可是能看出这个只是针对单个view去装备,假如咱们想其实页面一切view都要放快速点击,只不过某个view需求快速点击,比方抢东西类型的,那必定不能防。所以给每个view独自去装备就很费事,没关系,咱们能够优化一下

public class FastClickHelper {
    private Map<View, Integer> map;
    private HandlerThread mThread;
    public void init(ViewGroup viewGroup) {
        map = new ConcurrentHashMap<>();
        initThread();
        loopAddView(viewGroup);
        for (View v : map.keySet()) {
            v.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        int state = map.get(v);
                        if (state == 1) {
                            return true;
                        } else {
                            map.put(v, 1);
                            block(v);
                        }
                    }
                    return false;
                }
            });
        }
    }
    private void initThread() {
        mThread = new HandlerThread("LAZY_CLOCK");
        mThread.start();
    }
    private void block(View v) {
        // 切条线程处理
        Handler handler = new Handler(mThread.getLooper());
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (map != null) {
                    map.put(v, 0);
                }
            }
        }, 1000);
    }
    private void exclude(View... views) {
        for (View view : views) {
            map.remove(view);
        }
    }
    private void loopAddView(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            if (viewGroup.getChildAt(i) instanceof ViewGroup) {
                ViewGroup vg = (ViewGroup) viewGroup.getChildAt(i);
                map.put(vg, 0);
                loopAddView(vg);
            } else {
                map.put(viewGroup.getChildAt(i), 0);
            }
        }
    }
    public void onDestroy() {
        try {
            map.clear();
            map = null;
            mThread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我把viewgroup当成入参,然后给它的一切子view都设置,由于onclicklistener比较常用,所以改成了设置setOnTouchListener,当然外部假如给view设置了setOnTouchListener去掩盖我这的set,那就只能自己做特殊处理了。

在外部直接调用

FastClickHelper fastClickHelper = new FastClickHelper();
fastClickHelper.init((ViewGroup) getWindow().getDecorView());

假如要想让某个view不要约束快速点击的话,就调用exclude办法。这儿要注意运用完之后开释资源,要调用onDestroy办法开释资源。

关于这个部分的考虑,其实上面的我们都会,也基本是这样去约束,可是便是即便我用第二种代码,也要每个页面都调用一次,并且看起来,多少差点高雅。

首要我想的办法是在事情分发下发的进程去做处理,便是在viewgroup的dispatchTouchEvent或许onInterceptTouchEvent这类办法里边,可是我简略看了源码是没有供给办法出来的,也没有比较好去hook的地方,所以只能暂时放弃考虑在这个下发流程去做手脚。

弥补一下,假如你是自定义view,那必定不会烦恼这个问题,可是你总不能一切的view都做成自定义的吧。

其次我想怎么能经过不写逻辑代码能完成这个作用,但总觉得这个方向不便是AOP吗,或许不是经过开发层面,在开发完毕后想办法去注入字节码等操作,我觉得要往这个方向考虑的话,最终的完成必定不是代码层面去完成的。

(2)阻拦办法

上面也说了,相对于阻拦事情,假设假如都能完成的状况下,我更倾向于去阻拦办法。

由于从这层面上来说,假如完成阻拦办法,或许说能完成中断办法,那就不只是能做到防快速点击,而是能给办法去定制相对应的规则,比方某个办法在1秒的间隔内只能调用一次,这个便是防快速点击的作用嘛,比方某个办法我约束只能调一次,假如能完成,我就不必再额外写判别这个办法调用一次过后我设置一个布尔类型,然后下次调用再判别这个布尔类型来决定是否调用,

那现在是没办法完成阻拦办法吗?当然有办法,只不过会十分的不高雅,比方一个办法是这样的。

public void fun(){
    // todo 第1步
    // todo 第2步
    // todo ......
    // todo 第n步
}

那我能够封装一个类,里边去封装一些战略,然后根据战略再去决定办法要不要履行这些步骤,那或许就会写成

public void fun(){
    new FunctionStrategy(FunctionStrategy.ONLY_ONE, new CallBack{
        @Override
        public void onAction() {
            // todo 第1步
            // todo 第2步
            // todo ......
            // todo 第n步
        }
    })
}

这样就完成了,比方只调用一次,详细的只调用一次的逻辑就写在FunctionStrategy里边,然后第2次,第n次就不会回调。当然我这是随便乱下来表达这个思路,实际必定不能这样写。首要这样写就很不高雅,其次也会存在许多问题,扩展性也很差。

那在代码层面还有其它办法阻拦或许中断办法吗,在代码层还真有办法中断办法,没错,那便是抛反常,可是话说回来,你也不或许在每个地方都try-catch吧,不切实际。

目前对阻拦办法或许中断办法,我是没想到什么好的思路了,可是我觉得假如能完成,对避免快速点击来说,必定会是一个很好的方案。