前言
其實(shí)快速點(diǎn)擊是個很好解決的問題,但是如何優(yōu)雅的去解決確是一個難題,本文主要是記錄一些本人通過解決快速點(diǎn)擊的過程中腦海里浮現(xiàn)的一些對這個問題的深思。
作者:流浪漢kylin 鏈接:https://juejin.cn/post/7197337416096055351
1. AOP
可以通過AOP來解決這個問題,而且AOP解決的方法也很優(yōu)雅,在開源上也應(yīng)該是能找到對應(yīng)的成熟框架。
AOP來解決這類問題其實(shí)是近些年一個比較好的思路,包括比如像數(shù)據(jù)打點(diǎn),通過AOP去處理,也能得到一個比較優(yōu)雅的效果。牛逼的人甚至可以不用別人寫的框架,自己去封裝就行,我因為對這個技術(shù)棧不熟,這里就不獻(xiàn)丑了。
總之,如果你想快速又簡單的處理這種問題,AOP是一個很好的方案
2. kotlin
使用kotlin的朋友有福了,kotlin中有個概念是擴(kuò)展函數(shù),使用擴(kuò)展函數(shù)去封裝放快速點(diǎn)擊的操作邏輯,也能很快的實(shí)現(xiàn)這個效果。它的好處就是突出兩個字“方便”
那是不是我用java,不用kotlin就實(shí)現(xiàn)不了kotlin這個擴(kuò)展函數(shù)的效果?當(dāng)然不是了。這讓我想到一件事,我也有去看這類問題的文章,看看有沒有哪個大神有比較好的思路,然后我注意到有人就說用擴(kuò)展函數(shù)就行,不用這么麻煩。
OK,那擴(kuò)展函數(shù)是什么?它的原理是什么?不就是靜態(tài)類去套一層嗎?那用java當(dāng)然能實(shí)現(xiàn),為什么別人用java去封裝這套邏輯就是麻煩呢?代碼不都是一樣,只不過kotlin幫你做了而已。所以我覺得kotlin的擴(kuò)展函數(shù)效果是方便,但從整體的解決思路上看,缺少點(diǎn)優(yōu)雅。
3. 流
簡單來說也有很多人用了Rxjava或者kotlin的flow去實(shí)現(xiàn),像這種實(shí)現(xiàn)也就是能方便而已,在底層上并沒有什么實(shí)質(zhì)性的突破,所以就不多說了,說白了就是和上面一樣。
4. 通過攔截
因為上面已經(jīng)說了kt的情況,所以接下來的相關(guān)代碼都會用java來實(shí)現(xiàn)。
通過攔截來達(dá)到防止快速點(diǎn)擊的效果,而攔截我想到有2種方式,第一種是攔截事件,就是基于事件分發(fā)機(jī)制去實(shí)現(xiàn),第二種是攔截方法。
相對而言,其實(shí)我覺得攔截方法會更加安全,舉個場景,假如你有個頁面,然后頁面正在到計算,到計算完之后會顯示一個按鈕,點(diǎn)擊后彈出一個對話框。然后過了許久,改需求了,改成到計算完之后自動彈出對話框。但是你之前的點(diǎn)擊按鈕彈出對話框的操作還需要保留。那就會有可能因為某些操作導(dǎo)致到計算完的一瞬間先顯示按鈕,這時你以迅雷不及掩耳的速度點(diǎn)它,那就彈出兩次對話框。
(1)攔截事件
其實(shí)就是給事件加個判斷,判斷兩次點(diǎn)擊的時間如果在某個范圍就不觸發(fā),這可能是大部分人會用的方式。
正常情況下我們是無法去入侵事件分發(fā)機(jī)制的,只能使用它提供的方法去操作,比如我們沒辦法在外部影響dispatchTouchEvent這些方法。當(dāng)然不正常的情況下也許可以,你可以嘗試往hook的方向去思考能不能實(shí)現(xiàn),我這邊就不思考這種情況了。
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);
}
}
});
}
}
簡單來寫就是這樣,其實(shí)這個就和上面說的kt的擴(kuò)展函數(shù)差不多。調(diào)用的時候就
FastClickHelper.setOnClickListener(view, this);
但是能看出這個只是針對單個view去配置,如果我們想其實(shí)頁面所有view都要放快速點(diǎn)擊,只不過某個view需要快速點(diǎn)擊,比如搶東西類型的,那肯定不能防。所以給每個view單獨(dú)去配置就很麻煩,沒關(guān)系,我們可以優(yōu)化一下
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當(dāng)成入?yún)ⅲ缓蠼o它的所有子view都設(shè)置,因為onclicklistener比較常用,所以改成了設(shè)置setOnTouchListener,當(dāng)然外部如果給view設(shè)置了setOnTouchListener去覆蓋我這的set,那就只能自己做特殊處理了。
在外部直接調(diào)用
FastClickHelper fastClickHelper = new FastClickHelper();
fastClickHelper.init((ViewGroup) getWindow().getDecorView());
如果要想讓某個view不要限制快速點(diǎn)擊的話,就調(diào)用exclude方法。這里要注意使用完之后釋放資源,要調(diào)用onDestroy方法釋放資源。
關(guān)于這個部分的思考,其實(shí)上面的大家都會,也基本是這樣去限制,但是就是即便我用第二種代碼,也要每個頁面都調(diào)用一次,而且看起來,多少差點(diǎn)優(yōu)雅。
首先我想的辦法是在事件分發(fā)下發(fā)的過程去做處理,就是在viewgroup的dispatchTouchEvent或者onInterceptTouchEvent這類方法里面,但是我簡單看了源碼是沒有提供方法出來的,也沒有比較好去hook的地方,所以只能暫時放棄思考在這個下發(fā)流程去做手腳。
補(bǔ)充一下,如果你是自定義view,那肯定不會煩惱這個問題,但是你總不能所有的view都做成自定義的吧。
其次我想怎么能通過不寫邏輯代碼能實(shí)現(xiàn)這個效果,但總覺得這個方向不就是AOP嗎,或者不是通過開發(fā)層面,在開發(fā)結(jié)束后想辦法去注入字節(jié)碼等操作,我覺得要往這個方向思考的話,最終的實(shí)現(xiàn)肯定不是代碼層面去實(shí)現(xiàn)的。
(2)攔截方法
上面也說了,相對于攔截事件,假設(shè)如果都能實(shí)現(xiàn)的情況下,我更傾向于去攔截方法。
因為從這層面上來說,如果實(shí)現(xiàn)攔截方法,或者說能實(shí)現(xiàn)中斷方法,那就不只是能做到防快速點(diǎn)擊,而是能給方法去定制相對應(yīng)的規(guī)則,比如某個方法在1秒的間隔內(nèi)只能調(diào)用一次,這個就是防快速點(diǎn)擊的效果嘛,比如某個方法我限制只能調(diào)一次,如果能實(shí)現(xiàn),我就不用再額外寫判斷這個方法調(diào)用一次過后我設(shè)置一個布爾類型,然后下次調(diào)用再判斷這個布爾類型來決定是否調(diào)用,
那現(xiàn)在是沒辦法實(shí)現(xiàn)攔截方法嗎?當(dāng)然有辦法,只不過會十分的不優(yōu)雅,比如一個方法是這樣的。
public void fun(){
// todo 第1步
// todo 第2步
// todo ......
// todo 第n步
}
那我可以封裝一個類,里面去封裝一些策略,然后根據(jù)策略再去決定方法要不要執(zhí)行這些步驟,那可能就會寫成
public void fun(){
new FunctionStrategy(FunctionStrategy.ONLY_ONE, new CallBack{
@Override
public void onAction() {
// todo 第1步
// todo 第2步
// todo ......
// todo 第n步
}
})
}
這樣就實(shí)現(xiàn)了,比如只調(diào)用一次,具體的只調(diào)用一次的邏輯就寫在FunctionStrategy里面,然后第2次,第n次就不會回調(diào)。當(dāng)然我這是隨便亂下來表達(dá)這個思路,現(xiàn)實(shí)肯定不能這樣寫。首先這樣寫就很不優(yōu)雅,其次也會存在很多問題,擴(kuò)展性也很差。
那在代碼層面還有其它辦法攔截或者中斷方法嗎,在代碼層還真有辦法中斷方法,沒錯,那就是拋異常,但是話說回來,你也不可能在每個地方都try-catch吧,不切實(shí)際。文章來源:http://www.zghlxwxcb.cn/news/detail-692257.html
目前對攔截方法或者中斷方法,我是沒想到什么好的思路了,但是我覺得如果能實(shí)現(xiàn),對防止快速點(diǎn)擊來說,肯定會是一個很好的方案。文章來源地址http://www.zghlxwxcb.cn/news/detail-692257.html
到了這里,關(guān)于Android深思如何防止快速點(diǎn)擊的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!