註明:方案都是百度來的,我自己項目挑了方案3
1.方案1(監控最外層佈局)
1.1步驟
1. 給activity佈局文件加入 id
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical"
android:id="@+id/activity_main"
>
2. 按照一般控件的方式進行事件監聽。
activity_main=(LinearLayout) findViewById(R.id.activity_main);
activity_main.setOnTouchListener(new OnTouchListener()
{
public boolean onTouch(View arg0, MotionEvent arg1)
{
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
});
1.2測試結果
沒測試,不知道好用否,不過感覺有點粗糙
2.方案2(單個editText適用)
2.1步驟
1. 這個方法分發touchEvent,這裏用於攔截用戶觸摸事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 獲得當前得到焦點的View,一般情況下就是EditText(特殊情況就是軌跡求或者實體案件會移動焦點)
View v = getCurrentFocus();
if (isShouldHideInput(v, ev)) {
hideSoftInput(v.getWindowToken());
}
}
return super.dispatchTouchEvent(ev);
}
2. 判斷點擊的是否在當前的editText範圍內
/**
* 根據EditText所在座標和用戶點擊的座標相對比,來判斷是否隱藏鍵盤,因爲當用戶點擊EditText時沒必要隱藏
*
* @param v
* @param event
* @return
*/
private boolean isShouldHideInput(View v, MotionEvent event) {
if (v != null && (v instanceof EditText)) {
int[] l = { 0, 0 };
v.getLocationInWindow(l);
int left = l[0], top = l[1], bottom = top + v.getHeight(), right = left
+ v.getWidth();
if (event.getX() > left && event.getX() < right
&& event.getY()> top && event.getY() < bottom) {
// 點擊EditText的事件,忽略它。
return false;
} else {
return true;
}
}
// 如果焦點不是EditText則忽略,這個發生在視圖剛繪製完,第一個焦點不在EditView上,和用戶用軌跡球選擇其他的焦點
return false;
}
3. 隱藏鍵盤方法(可丟到工具類)
/**
* 多種隱藏軟件盤方法的其中一種
*
* @param token
*/
private void hideSoftInput(IBinder token) {
if (token != null) {
InputMethodManager im = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
im.hideSoftInputFromWindow(token,
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
2.2測試結果
測試通過,單個editText效果完美;
但如果界面有多個editText的話,從這個editText點擊另外一個editText的話,就會讓虛擬鍵盤收縮隨機再彈出來,這樣一來一回,用戶體驗就糟糕了,尤其手機卡的話
3.方案3
3.1步驟
@Override
public boolean onTouchEvent(MotionEvent event) {
if(null != this.getCurrentFocus()){
View v = getCurrentFocus();
CommonUtils.closeSoftInput(LoginActivity.this, v.getWindowToken());
}
return super .onTouchEvent(event);
}
3.2測試結果
測試通過,也是簡單粗暴
原理就是,當點擊控件的話,touchEvent就會被消費了,這個方法就不會被調用;而點擊空白處,這個Activity裏的方法就會被調用
下一步講一下touchEvent的流程。
4.觸摸事件分發
4.1什麼是Touch事件
- Touch事件是由一個ACTION_DOWN,n個ACTION_MOVE,一個ACTION_UP組成onClick,onLongClick,onScroll等事件
- 看完下面幾個函數就知道,由於觸摸動作包括幾個touch事件組成的,而攔截只攔截一個事件(舉個栗子,可能按下去這個view處理,但鬆開就不是由這個view處理了)
- 而控件分爲兩種:
- 一種是繼承View不能包含其他控件的控件
- 一種是繼承ViewGroup可以包含其他控件的控件,暫且稱爲容器控件,比如ListView,GridView,LinearLayout等
4.2核心函數
4.21分發TouchEvent
publicboolean dispatchTouchEvent (MotionEvent ev)
來看Activity中的解釋:
- 它會被調用來處理觸摸屏事件,可以重寫此方法來攔截所有觸摸屏事件(在這些事件分發到這個窗口之前)
- 通常應該處理觸摸屏事件,一定要調用這個實現。當返回值爲true時,表示這個事件已經被消費了
- 例如在TextActivity中dispatchTouchEvent在ACTION_MOVE返回true,運行結果如下:
也就是說,它並沒有把那ACTION_MOVE分發下去。
4.22攔截TouchEvent(ViewGroup獨有的,View沒有,Activity自然也沒有)
public booleanonInterceptTouchEvent(MotionEvent ev)
基本就是:
- ACTION_DOWN首先會傳遞到onInterceptTouchEvent()方法
- 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後returnfalse,那麼後續的move,up等事件將繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目標view的onTouchEvent()處理(這段話的意思就是,大爺我依然是優先級最高的)
- 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後returntrue,那麼後續的move,up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件(真被攔截了)
- 如果最終需要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理
- 如果最終需要處理事件的view的onTouchEvent()返回了true,那麼後續事件將可以繼續傳遞給該view的onTouchEvent()處理
4.23處理TouchEvent
publicboolean onTouchEvent(MotionEvent ev)
這個就沒啥好說了,處理點擊的
4.3觸摸事件默認處理流程
縱觀全圖,可發現攔截(紅色線條)一條線,從上到下,然後處理(粉色線條)一條線,從下到上。
說明:
1) 當觸摸事件ACTION_DOWN發生之後,先調用Activity中的dispatchTouchEvent函數進行處理
緊接着ACTION_DOWN事件傳遞給ViewGroup中的dispatchTouchEvent函數,接着ViewGroup中的dispatchTouchEvent中的ACTION_DOWN事件傳遞到調用ViewGroup中的onInterceptTouchEvent函數,此函數負責攔截ACTION_DOWN事件:
- 由於ViewGroup下還能包含子View,所以默認返回值爲false,即不攔截此ACTION_DOWN事件
- 返回false,則ACTION_DOWN事件繼續傳遞給其子view
- 由於子view不是viewGroup的控件,所以ACTION_DOWN事件接着傳遞到onTouchEvent進行處理事件。此時消息的傳遞基本上結束
2) 從上可以分析,motionEvent事件的傳遞是採用隧道方式傳遞。隧道方式,即從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞
3) 接下來繼續分析,事件的處理。剛纔ACTION_DOWN事件傳遞到view的onTouchEvent函數中處理了,默認是返回true,接着view的dispatchTouchEvent返回true,再接着viewGroup的dispatchTouchEvent返回true,最後Activity的dispatchTouchEvent返回true。我們發現,motionEvent事件的處理採用冒泡方式。冒泡方式,從最內層子元素依次往外傳遞直到根元素或在中間某一元素中由於某一條件停止傳遞。
4.5一些例子
現在我們來做一些改變,就接着以ACTION_DOWN爲例
4.5.1情況一:
我們在View中onTouchEvent中ACTION_DOWN返回false,輸出結果如下:
可以發現ACTION_DOWN事件傳遞到上層的ViewGroup的onTouchEvent,同時返回true,說明事件被ViewGroup消費了。同時之後的touch事件(ACTION_MOVE等)不再傳遞給view,只傳遞到ViewGroup,由ViewGroup的onTouchEvent函數處理touch事件。同時onInterceptTouchEvent也不再調用。
4.5.2情況二:
我們在View中onTouchEvent中ACTION_MOVE返回false,輸出結果如下:
由於view未消費此ACTION_MOVE事件,按照原理來說應該是將事件處理冒泡到ViewGroup去處理,但結果卻是Activity處理的。我們知道,觸摸事件首先發生的就是ACTION_DOWN事件,我們在onInterceptTouchEvent所解釋就可以發現ACTION_DOWN與ACTION_MOVE等事件有區別,ACTION_DOWN事件作爲起始事件,它的重要性是要超過ACTION_MOVE和ACTION_UP的,如果發生了ACTION_MOVE或者ACTION_UP,那麼一定曾經發生了ACTION_DOWN。也就是說ACTION_DOWN事件被view消費了,而ACTION_MOVE事件沒被消費,傳遞到ViewGroup,由於之前ViewGroup沒處理ACTION_DOWN事件,所以它也不處理ACTION_MOVE。但Activity卻不一樣,它可以接受所有事件。
4.5.3情況三:
這次在ViewGroup中的onInterceptTouchEvent中ACTION_DOWN返回true
結果如下:
它直接把事件發送給ViewGroup的onTouchEvent處理,此後不再攔截事件直接到viewGroup中的onTouchEvent處理。
4.5.4情況四:
在ViewGroup中的onInterceptTouchEvent中ACTION_MOVE返回true
結果如下:
ACTION_MOVE被ViewGroup攔截了,上次處理ACTION_DOWN的view則會收到ACTION_CANCEL事件,之後ViewGroup不再攔截後續事件,事件直接在ViewGroup中的onTouchEvent處理