Android点击空白区域收起键盘

本文内容

  1. 可以添加蒙板来处理点击,以及如何把输入框顶到键盘上面
  2. 手动关闭键盘的时机问题
  3. EditText第一次点击不响应的解决方法
  4. 通过拦截Activity分发事件来判断此时点击的是EditText还是其他区域,并分别做处理的方法(主要是要说这个)
  5. 监听键盘打开的方法
  6. 一些其他的,关于EditText的焦点问题

更新:提供了一个判断键盘是否正处于弹出状态的方法,更新在4下面了,这个新的方法不能实时监听,但是优点在于不像addOnGlobalLayoutListener那样调用次数过多浪费性能

老样子,本文代码都是随便写的,想拿去用随便搜搜或者点链接,哪都有

参考链接以及问题描述

Android点击屏幕空白处,隐藏键盘 - yang_xing
总之就是点击没有EditText的地方就把键盘隐藏起来,根据这个思路我们也可以多做些别的

这里我搞了些多余的操作,因为我这边EditText的焦点不知道为什么怎么也取消不掉,关于这个问题文章末尾有提到
顺带一提,关闭键盘的时机最好在Activity的onDestroy方法之前,在onDestroy时Activity已经不在窗口当中了,关闭键盘是要传入Context的,此时传入的和当前窗口中显示的自然不是同一个Activity了,会导致键盘无法关闭。当然,更省事的办法是在Manifest里面定义stateHidden就好了,这表示进入此Activity时键盘默认会关闭,只有主动点击EditText等方法才会将其打开。

问题描述:

现在基本上绝大部分App都会有在调出键盘时点击屏幕任意区域将键盘取消掉的功能,方法各不相同吧,这个具体看设计。
下面我会写点别的,不感兴趣可以直接跳到下面的解决方案都不感兴趣请直接点上面那个链接emm
分情况大概说一下(下面的代码是我随便写的):

  1. 添加蒙版,键盘上面是输入框,输入文字之后出现一层蒙板,点发送或者点蒙板都会让键盘消失。
View maskView = new View(getApplicationContext());
        addContentView(maskView, new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 
                ViewGroup.LayoutParams.MATCH_PARENT));
        maskView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               ... // hide your keyboard or do something
            }
        });

想要输入框到键盘上面这事其实很简单,在输入框所在的布局文件的外层定义android:fitsSystemWindows="true"就好了,这样键盘顶起来的时候会将最底下的输入框顶上去的。顺便一提,能滚动的布局请把设置成AdjustResize,不能滚动的请设置成AdjustPan,如果我记反了的话换了试试就好了emm

大概就是这种感觉android:windowSoftInputMode="adjustResize|stateHidden"

  1. 点了EditText弹出来一个Dialog用来输入内容
    由于EditText在没焦点时设置OnClick之后第一件事是给它个焦点,于是你的点击事件就这么被消耗了,要避免这事的话几种方法
    • 鱼死网破法:给EditText设置 onFocusableInTouchMode = false,设置完这个EditText就变成能点不能输的控件了,其实如果是弹个dialog另行输入的话这也不失为一个办法,顺便在父布局那里搞上descendantFocusability="blockDescendants"也可以
    • 曲线救国法:不设置onClickListener,然后设置onTouchListener或者onFocusChangeListener,思路大概就是因为第一次点击事件给focus消耗掉了,所以自然会出现focuschange,onTouch的话,DOWN被消耗掉了还有UP呢
    • 偷天换日法:上面蒙层view,给那个view设置点击事件(感觉这个办法好蠢……)
    • 画地为牢法:外面嵌套一层Layout,设置onIntercept
    • 曲线救国和画地为牢的示例代码我补在后面,感兴趣可以翻过去看看
  2. 屏幕里好大一张图,能点,下面是EditText,EditText旁边是用来发送的Button(类似我这的情况)


    大概长这样

具体可以参考最上面的链接,我自己改了改用来处理我自己的情况
目的:在呼起键盘的时候,点击EditText和点击按钮有效,且如果没有输入的内容,键盘不会收下去。点击其他区域无效,键盘会收起来。

解决方案

思路:拦截Activity的事件分发,如果此时屏幕焦点在EditText上面(如果光标在闪就说明焦点在它那儿了),
具体来说的话,我希望上面的图不能点,但是EditText和右边的箭头都可以点,那么监听点击事件的位置,同时计算EditText的位置,如果点击事件位置在里面,那就调出键盘,再点什么也不做,点外面就把键盘收起来,其中上下的话,事件会被拦截,左右的话,事件可以继续处理。

public static boolean hideKeyboardByClick(MotionEvent event, View view, Activity activity) {
        try {
            if (view instanceof EditText) { // EditText获取焦点时
                int[] location = { 0, 0 };
                view.getLocationInWindow(location);
                int top = location[1],
                        bottom = top + view.getHeight();
            // 判断焦点位置坐标是否在空间内,如果位置在控件外,则隐藏键盘
                if (event.getRawY() < top || event.getRawY() > bottom) {
                    // 在上下
                    hideSoftInputBoard(activity, null);
                    return true;
                } // 在EditText的横向范围内,什么都不做
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

外面:(这里用了写在监听键盘那里的方法来做判断)

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                View view = getCurrentFocus();
                if (isKeyboardShow && Utils.hideKeyboardByClick(ev, view, MainActivity.this)) { // 调用方法判断是否需要隐藏键盘
                    return true;
                }
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

如果你没什么好办法监测键盘的话,下面有个麻烦的方法,仅供参考:
再下面更新了一个靠谱一点的办法,下面这些可以跳过了
Android里面静态变量尽量少用,毕竟用起来逻辑很容易乱,又占空间,不过当作常量的final static倒是无所谓

// 用来记录键盘是否打开
public static boolean isEditSelected = false;

public static boolean hideKeyboard(MotionEvent event, View view,
                                Activity activity) {
    try {
        if (view instanceof EditText) { // EditText获取焦点时
            int[] location = { 0, 0 };
            view.getLocationInWindow(location);
            int left = location[0], top = location[1], right = left
                    + view.getWidth(), bottom = top + view.getHeight();
            // 判断焦点位置坐标是否在空间内,如果位置在控件外,则隐藏键盘
            if (!isEditSelected) {
                // 没被选中的状态下,点哪里都有效
                if (event.getRawY() > top && event.getRawY() < bottom
                        && event.getRawX() > left && event.getRawX() < right) {
                    // 在EditText内,设置为选中状态
                    isEditSelected = true;
                }
            } else { // 如果在选中状态下,点击EditText的上下会隐藏键盘 + 失效,点击左右隐藏键盘但不失效,因为必定隐藏键盘
                isEditSelected = false;
                if (event.getRawY() < top || event.getRawY() > bottom) {
                    // 在上下
                    hideSoftInputBoard(activity, null);
                    return true;
                } else if (event.getRawX() > left && event.getRawX() < right) {
                    // 在EditText内,什么都不做
                    isEditSelected = true;
                    return false;
                }
                // 不在上下,不在内部,但在左右,正常生效,但隐藏键盘
                hideSoftInputBoard(activity, null);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

在这里说一下,如果键盘的弹出方式是将整个界面顶上去的话,最好用getRawY(),因为键盘收取弹出会带来Padding的变化……

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 其实监听DOWN基本上就是监听所有触摸相关的事件了
            View view = getCurrentFocus();
            if (hideKeyboard(ev, view, MainActivity.this)) { // 调用方法判断是否需要隐藏键盘
                // 这里返回true代表事件不再下发,意思是子控件不会响应点击事件
                return true;
            }
            break;
        default:
            break;
    }
    return super.dispatchTouchEvent(ev);
}

除此之外,要考虑到EditText里面有内容时点击按钮是会收起键盘的,所以在按钮的点击事件里要把相应的值给调回去。
另外,要考虑到长按的事件,也要考虑到EditText在没有焦点时点上去的问题,总之上代码。

sendBtton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String message = editText.getText().toString();
                if (Utils.isFastDoubleClick() || Utils.isEmpty(message)) {
                    // 上面这两个方法是我自己的,用来判断输入是否为空和是否连点两次
                    return;
                }
                Utils.isEditSelected = false;
                Utils.hideSoftInputBoard(); // 收起键盘,和那个hideKeyBoard方法是两个不同的方法
                ... // 点击事件
            }
        });
editText.setOnTouchListener(new View.OnTouchListener() {
            long preClickTime = 0;
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (MotionEvent.ACTION_DOWN == event.getAction()) {
                    preClickTime = System.currentTimeMillis();
                }
                if (MotionEvent.ACTION_UP == event.getAction()) {
                    if (System.currentTimeMillis() - preClickTime > 500) {
                        Utils.isEditSelected = false; // 记得改成自己的类
                        // 长按不打开键盘
                        return false;
                    }
                    Utils.isEditSelected = true; // 手从EditText上抬起,相当于点击
                }
                return false;
            }
        });

其实要收起键盘,输入法里面还自带个收键盘按钮……所以说
实际上这个方法有点蠢,要考虑的情况太多了,真要用还是和下面的监听键盘状态一起用会更好一些
or 如果谁有什么好办法监听键盘的话,欢迎提出~

更新:上面之前拿来判断键盘的是个boolean值吧,现在有个更好点的办法来判断键盘是不是在弹出状态了
思路来自github项目:
KeyboardVisibilityEvent

总体很简单,就是判断屏幕高度和当前显示的高度差了多少,我自己测了下,挺靠谱的

    public boolean isKeyboardVisible(Activity activity) {
        double KEYBOARD_MIN_HEIGHT_RATIO = 0.3;
        Rect r = new Rect();
        View activityRoot = getActivityRoot(activity);
        activityRoot.getWindowVisibleDisplayFrame(r);
        int screenHeight = activityRoot.getRootView().getHeight();
        int heightDiff = screenHeight - r.height();
        return heightDiff > screenHeight * KEYBOARD_MIN_HEIGHT_RATIO;
    }

    private View getActivityRoot(Activity activity) {
        return ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
    }

Android监听键盘状态

Android 监听键盘弹出收起 - 掘金
这个我懒得写太多,看链接就完事儿了,思路是监听View在窗口中的大小变化来判断键盘高度
Android点击空白区域收起键盘
这个是我把下面的内容整合了一下,自己写了一段

        final View rootView = getActivity().getWindow().getDecorView();
        globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            private int rootViewVisibleHeight = 0;
            @Override
            public void onGlobalLayout() {
                Rect r = new Rect();
                rootView.getWindowVisibleDisplayFrame(r);
                int visibleHeight = r.height();
                System.out.println(""+visibleHeight);
                if (rootViewVisibleHeight == 0) {
                    rootViewVisibleHeight = visibleHeight;
                    return;
                }
                //根视图显示高度没有变化,可以看作软键盘显示/隐藏状态没有改变
                if (rootViewVisibleHeight == visibleHeight) {
                    return;
                }
                //根视图显示高度变小超过200,可以看作软键盘显示了
                if (rootViewVisibleHeight - visibleHeight > 200) {
                    rootViewVisibleHeight = visibleHeight;
                    ... // do whatever you want
                    return;
                }
                //根视图显示高度变大超过200,可以看作软键盘隐藏了
                if (visibleHeight - rootViewVisibleHeight > 200) {
                    rootViewVisibleHeight = visibleHeight;
                     ... // do whatever you want
                }
            }
        };
        rootView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);

这是一个会连续调用的方法,调用的时机是窗口中任何view重绘的时候,所以判断条件要全面一些,根据自己需要把具体方法放到具体情况底下
如果要计算滚动高度的话,屏幕高度 - (键盘高度 - EditText底部高度)就好了,要考虑StatusBar高度可以考虑用反射获取:

android开发:获取键盘的高度 - CSDN
随便找了一个,这个考虑到statusbar了

try {
    Class<?> c = Class.forName("com.android.internal.R$dimen");
    Object obj = c.newInstance();
    Field field = c.getField("status_bar_height");
    int x = Integer.parseInt(field.get(obj).toString());
    statusBarHeight = context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
    e.printStackTrace();
}

顺便,Listener不用时记得回收:
view.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);这个最好注册给Activity,所以onPause和onDestroy时去掉就好了,onResume时可以考虑重新注册上。
如果只想使用一次,可以直接在方法里面remove掉,或者干脆点使用view的post()方法

在这里补一下曲线救国和画地为牢法的代码

(没人忘了这俩是指啥吧……)
其实就随便写两行谁都会的代码而已:

  1. 曲线救国法
editText.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (MotionEvent.ACTION_UP == event.getAction()) {
                   ... // do somethings
                }
                return false;
            }
        });
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                   ... // do something
                }
            }
        });
  1. 画地为牢法
public class EditIntercpetLayout extends FrameLayout {

        public EditIntercpetLayout(@NonNull Context context) {
            super(context);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            ... // do something
            // 这里的返回值true代表事件在这里消耗,即子控件不会再获取事件,如果不想这样,不修改返回值就好
            return super.onInterceptTouchEvent(ev);
        }
    }

其他

关于EditText焦点的问题

StackOverFlow - How to remove focus from single editText

总结一下,一般而言素质三连就好了
android:focusable="true"
android:focusableInTouchMode="true"
view.clearFocus()
需要注意的是那两条属性要配置在父布局里,否则的话focus清掉还会重新寻找焦点,如果没有父布局有这些属性,那focus就会回到EditText那里
如果在父布局中设置这些不生效的话:
这是我遇到的问题,最后的解决办法:
在EditText旁边创建一个不可见也基本不占位置的view,素质三连,然后在布局加载完成后requestFocus就好了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容