1. 程式人生 > >android 點選EditText彈鍵盤,點選其他控制元件收鍵盤不觸發點選事件,特1控制元件收鍵盤觸發點選,特2控制元件點選不收鍵盤

android 點選EditText彈鍵盤,點選其他控制元件收鍵盤不觸發點選事件,特1控制元件收鍵盤觸發點選,特2控制元件點選不收鍵盤

說實話開發android,鍵盤真的很不聽話,非常難用,於是百度找到dispatchTouchEvent進行重寫可完成鍵盤收放,但還是不夠完美,我期望的功能如下:
1.基礎功能:
a.點選輸入控制元件彈出鍵盤
b.點選非輸入控制元件收起鍵盤

2.特殊功能1:
特殊按鈕點選時除了收起鍵盤也能觸發其本來點選事件,例如登入按鈕,輸入完資訊後,我希望點選登入時能夠收起鍵盤並且觸發點選事件(不是所有都需要,比如列表上方有個搜尋,點選搜尋輸入框彈出鍵盤,然後點選列表我期望只是收起鍵盤,不要進入列表詳情)

3.特殊功能2:
特殊按鈕點選時我希望不要收起鍵盤。例如一個輸入金額的輸入框比較小,我希望點選整行都可以彈出鍵盤輸入框獲取焦點,這種情況不要收鍵盤。(不是所有都需要,大多時候我希望收起鍵盤,方便點選手機全屏)

於是創造出瞭如下程式碼,所有Activity繼承此類:

public class BaseActivity{
 protected boolean beforeEdit = false;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            View v = getViewAtViewGroup((int) (ev.getRawX()), (int) (ev.getRawY()));

            boolean
specialInput = getSpecialInput(v); if (isShouldHideInput(v, ev) && !specialInput) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null && isSoftShowing()) { imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0
); } } if ((v != null && v instanceof EditText)||specialInput) { beforeEdit = true; } else if (beforeEdit) { beforeEdit = false; if (v != null && (saveGetStringFromObj(v.getTag())).equals("click")) return super.dispatchTouchEvent(ev); if (isSoftShowing()) { return false; } } return super.dispatchTouchEvent(ev); } // 必不可少,否則所有的元件都不會有TouchEvent了 return super.dispatchTouchEvent(ev); } protected boolean isSoftShowing() { //獲取當前螢幕內容的高度 int screenHeight = getWindow().getDecorView().getHeight(); //獲取View可見區域的bottom Rect rect = new Rect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); return screenHeight - rect.bottom != 0; } private boolean getSpecialInput(View v) { if(v!=null){ Object tag = v.getTag(); if(tag!=null && (tag instanceof String) && ((String)tag).equals("input")){ return true; } } return false; } private String saveGetStringFromObj(Object tag) { if (tag == null) return ""; if (tag instanceof String) return (String) tag; return ""; } public boolean isShouldHideInput(View v, MotionEvent event) { if (v != null && (v instanceof EditText)) { return false; } return true; } } /** * 根據座標獲取相對應的子控制元件<br> * 在重寫ViewGroup使用 * * @param x 座標 * @param y 座標 * @return 目標View */ public View getViewAtViewGroup(int x, int y) { return findViewByXY(getRootView(this), x, y); } private View findViewByXY(View view, int x, int y) { View targetView = getTouchTarget(view,x,y); if(targetView == null) return null; else if(targetView instanceof ViewGroup){ ViewGroup v = (ViewGroup) targetView; for(int i = 0; i < v.getChildCount(); i++){ View tempView = findViewByXY(v.getChildAt(i),x,y); if(tempView!=null){ return tempView; } } } return targetView; } private static View getRootView(Activity context) { return ((ViewGroup) context.findViewById(android.R.id.content)).getChildAt(0); } private View getTouchTarget(View view, int x, int y) { View targetView = null; // 判斷view是否可以聚焦 ArrayList<View> TouchableViews = view.getTouchables(); for (View child : TouchableViews) { if (isTouchPointInView(child, x, y)) { targetView = child; break; } } return targetView; } private boolean isTouchPointInView(View view, int x, int y) { int[] location = new int[2]; view.getLocationOnScreen(location); int left = location[0]; int top = location[1]; int right = left + view.getMeasuredWidth(); int bottom = top + view.getMeasuredHeight(); if (view.isClickable() && y >= top && y <= bottom && x >= left && x <= right) { return true; } return false; }

以上程式碼即可搞定,如果需要特殊功能1,則將控制元件android:tag=”click”,如需特殊功能2,則將控制元件android:tag=”input”即可。
這裡補充一點,特殊功能2的,除了要寫android:tag=”input”外還要:

view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                editText.requestFocus();
                //這裡的判斷為了優化視覺效果,不新增則有可能收起再彈出
                if(!isSoftShowing()){
                    InputMethodManager imm = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.toggleSoftInput(0,InputMethodManager.SHOW_FORCED);
                }
            }
        });

在外面寫的原因是需要指定editText,另外,如果鍵盤已經再顯示會自動切換成合適的輸入內容,此時在彈出鍵盤會出現鍵盤收起再閃爍的情況,所以這裡需要判斷一下。

原理,不需要的拷貝上面程式碼即可

這裡解釋一下能夠控制鍵盤的原理,主要是下面這段程式碼:

 View v = getViewAtViewGroup((int) (ev.getRawX()), (int) (ev.getRawY()));

            boolean specialInput = getSpecialInput(v);

            if (isShouldHideInput(v, ev) && !specialInput) {
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                if (imm != null && isSoftShowing()) {
                    imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
                }
            }

            if ((v != null && v instanceof EditText)||specialInput) {
                beforeEdit = true;
            } else if (beforeEdit) {
                beforeEdit = false;
                if (v != null && (saveGetStringFromObj(v.getTag())).equals("click"))
                    return super.dispatchTouchEvent(ev);
                if (keyboardShowing) {
                    return false;
                }
            }
            return super.dispatchTouchEvent(ev);

一下這句用於獲取絕對座標對應的view,我之前在網上copy的版本這一句是不適配viewgroup的,只能適合非viewgroup的view,這裡進行了一定的改造,稍後再說。

 View v = getViewAtViewGroup((int) (ev.getRawX()), (int) (ev.getRawY()));

鍵盤管理

下面這幾句,意思是,先獲取view是否是特殊view(不收起鍵盤的view),如果不是特殊view,也不是edittext,並且鍵盤是顯示狀態則收起鍵盤。

 boolean specialInput = getSpecialInput(v);
if (isShouldHideInput(v, ev) && !specialInput) {
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                if (imm != null && isSoftShowing()) {
                imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
                }
            }

事件分發管理

  • 對於edittext和觸發edittext的特殊控制元件(點選不收鍵盤),設定beforeEdit標誌,並且正常分發;
  • 對於之前設定了beforeEdit標誌的,非特殊控制元件(點選收起鍵盤並觸發),不進行分發;
  • 否則正常分發事件。
if ((v != null && v instanceof EditText)||specialInput) {
                beforeEdit = true;
            } else if (beforeEdit) {
                beforeEdit = false;
                if (v != null && (saveGetStringFromObj(v.getTag())).equals("click"))
                    return super.dispatchTouchEvent(ev);
                if (keyboardShowing) {
                    return false;
                }
            }
            return super.dispatchTouchEvent(ev);

查詢座標控制元件

  • 檢視是否觸控點在view中,不是則直接返回null
  • 如果是viewgroup則逐一查詢,如果找到有更具體的子view在這個位置,則返回這個view,否則就返回view。
    通過這種方式就能實現逐一查詢,而且在找不到view的情況下,能返回最小的viewgroup,足夠用了。當然,這種情況不適合多控制元件重疊,但是基本夠用了。點選找到滿足點選位置的最小控制元件,足夠點選事件用了。其他情況可以重寫dispatchTouchEvent再次處理,我暫時沒遇到這種情況,如有遇到,就再寫一篇。
private View findViewByXY(View view, int x, int y) {
        View targetView = getTouchTarget(view,x,y);
        if(targetView == null)
            return null;
        else if(targetView instanceof ViewGroup){
            ViewGroup v = (ViewGroup) targetView;
            for(int i = 0; i < v.getChildCount(); i++){
                View tempView = findViewByXY(v.getChildAt(i),x,y);
                if(tempView!=null){
                    return tempView;
                }
            }
        }
        return targetView;
    }