背景需求
在用戶提交表單內(nèi)容,請求接口的通用場景下,增刪改查 crud 操作是比較頻繁出現(xiàn)的操作。
對于改查操作來講,并不會引起數(shù)據(jù)上的問題,影響的最多是接口重復調(diào)用;但是對于增刪操作來講,很容易引起數(shù)據(jù)重復添加,刪除異常等問題。
對用戶來講,網(wǎng)絡(luò)延時或者不穩(wěn)定造成請求接口時出現(xiàn)的App反應(yīng)遲緩,下意識的會進行重復提交;或者用戶不小心進行的重復點擊;又或者測試App自動化測試工具導致的短時間內(nèi)大量重復點擊。
這些場景都應(yīng)該在代碼實現(xiàn)的時候應(yīng)該考慮的。
1. “笨”方法實現(xiàn)
說是笨方法,當然也是出現(xiàn)重復提交問題時候,想到的最有效的方法。
1.1 狀態(tài)標記
對于每個點擊按鈕,初始狀態(tài) isPressed = FALSE 進行狀態(tài)判定,點擊一次記錄狀態(tài) isPressed = TURE ,然后進行接口請求或者其它處理,接口請求完成之后初始化該按鈕狀態(tài) isPressed = FALSE 。
1.2 狀態(tài)標記高級版
Android 中怎樣設(shè)置按鈕不能點擊?
其實,和上面的方法沒什么區(qū)別,只不過借助了** Button **的屬性設(shè)置來完成的。
Button.setEnabled(false);//設(shè)置這個屬性
思路和1.1一致,生了一個局部變量,依然有效!
2. Clever方法實現(xiàn)
其實,對于重復點擊這類問題,只需要一個狀態(tài)標記就能夠解決,但是我們的之前的方法只適用于單個** Button **按鈕,對于多個按鈕,如果我們這么去做,必然是大量的 Ctrl+C 和 Ctrl+V,想想都心累,更別說我還真的這么干過。
如果有一種一勞永逸的方法豈不是很好,對了,然后我們想到了封裝,從** Button 組件內(nèi)部去判斷豈不是美妙,這樣我們只需要替換xml中的 Button 就可以完美的解決大量的按鈕點擊事件。
既然, Button **按鈕的事件點擊是通過
setOnClickListener(OnClickListener listener)
方法注冊的,我們只需要在注冊的時候,替換新的Listener,加上我們自定義事件判斷就可以搞定。
比如:
public class OnNoRepeatButton extends Button
{
public ButtonEx(final Context context)
{
super(context);
}
public ButtonEx(final Context context, final AttributeSet attrs)
{
super(context, attrs);
}
public ButtonEx(final Context context, final AttributeSet attrs, final int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
@Override
public void setOnClickListener(final OnClickListener listener)
{
super.setOnClickListener(new OnNoRepeatClickListener(listener));
}
}
我們的** OnNoRepeatClickListener **:
/**
* 防止快速點擊中多次觸發(fā)事件的自定義OnClickListener
*/
public class OnNoRepeatClickListener implements OnClickListener
{
private final OnClickListener mListener;
private long lastClickTime = 0;
public OnNoRepeatClickListener(final OnClickListener listener)
{
this.mListener = listener;
}
@Override
public void onClick(final View v)
{
synchronized (this)
{
//判斷當前點擊時間與上一次點擊時間的間隔
if (System.currentTimeMillis() - this.lastClickTime > 1000)
{
if (this.mListener != null)
this.mListener.onClick(v);
//處理完響應(yīng)事件后記錄時間確保下次響應(yīng)
this.lastClickTime = System.currentTimeMillis();
}
}
}
}
Note: 代碼中的** 1000 **單位是ms,具體的時間需要根據(jù)需求來定義。
3. Smart方法實現(xiàn)
Clever方法已經(jīng)算是一個相對好的解決思路,但是如果我們的** Textview ,ImageView , View **等組件點擊事件重復需要怎么解決呢?
也需要我們重寫每一個的組件的
setOnClickListener(OnClickListener listener)
來實現(xiàn)。
那么,如果我們在重寫了** Activity/Fragment **中重寫
View.OnClickListener
方法,對點擊事件時間進行判斷,便可以減少對** View **組件的
setOnClickListener(OnClickListener listener)
方法的重寫。
上代碼:
interface IBaseView extends View.OnClickListener {
/**
* 初始化數(shù)據(jù)
*
* @param bundle 傳遞過來的bundle
*/
void initData(final Bundle bundle);
/**
* 綁定布局
*
* @return 布局Id
*/
int bindLayout();
/**
* 初始化view
*/
void initView(final Bundle savedInstanceState, final View view);
/**
* 業(yè)務(wù)操作
*/
void doBusiness();
/**
* 視圖點擊事件
*
* @param view 視圖
*/
void onWidgetClick(final View view);
}
BaseActivity.java
/**
* <pre>
* desc : Activity基類
* </pre>
*/
public abstract class BaseActivity extends AppCompatActivity
implements IBaseView {
/**
* 當前Activity渲染的視圖View
*/
protected View contentView;
/**
* 上次點擊時間
*/
private long lastClick = 0;
protected BaseActivity mActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = this;
Bundle bundle = getIntent().getExtras();
initData(bundle);
setBaseView(bindLayout());
initView(savedInstanceState, contentView);
doBusiness();
}
protected void setBaseView(@LayoutRes int layoutId) {
setContentView(contentView = LayoutInflater.from(this).inflate(layoutId, null));
}
/**
* 判斷是否快速點擊
*
* @return {@code true}: 是<br>{@code false}: 否
*/
private boolean isFastClick() {
long now = System.currentTimeMillis();
if (now - lastClick >= 800) {
lastClick = now;
return false;
}
return true;
}
@Override
public void onClick(final View view) {
if (!isFastClick()) onWidgetClick(view);
}
}
總結(jié)
當然,Smart方法也不是最佳的解決方案,這樣來做需要組件采用
setOnClickListener(this)
調(diào)用方式,讓** Activity/Fragment **統(tǒng)一處理點擊事件的響應(yīng),Clever和Smart的結(jié)合也是一個不錯的選擇。
Have fun , enjoy Coding...