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;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值