Android 软键盘隐藏寻找最优解

Android 软键盘隐藏寻找最优解

本文原创,转载请注明出处。
欢迎关注我的 简书 ,关注我的专题 Android Class 我会长期坚持为大家收录简书上高质量的 Android 相关博文。

写在前面:
最近我自己的开发任务接近尾声,提交测试之后收到了一个 bug,这个 bug 描述起来是这个样子的:

希望当点击外部空白区域软键盘隐藏的时候,EditText 的光标也消失。

当我看到这个 bug 的时候,心里想,额...应该不难吧,隐藏软键盘大家都会,那当我隐藏软键盘的时候,让 EditText 的 Cursor 消失就不好了?
事实上解决这个问题确实不难,但是作为一个稍微有点追(jiao)求(qing)的程序员,其实解决这个问题,还是经历了一些思考过程的,所以我把它整理出来,分享给大家。

先来看看这个 bug 的描述:当软键盘隐藏,光标消失。

测试的这段描述直接对我这种心思单纯的程序猿造成了误导,因为它直接把我的思路引到了光标的处理上:

先不说软键盘了,直接看看处理 cursor 是什么效果:

et1 隐藏光标
et2 不作处理

这个 Demo 项目我目前有两个 EditText et1,et2,还有一个不做任何处理的 button,此时我仅仅给 et1 隐藏光标 cursor,调用 et1.setCursorVisible(false),可以看到上图的效果,et1 的光标消失了。

head da

是啊通常我们项目里面的 EditText 只有一个光标,那光标是消失了,万一底下有那条线呢?不管了?

不要说再隐藏下面那条线就 ok 了,这样一来就太复杂了,说明我们思考的出发点有问题。好吧我们试图将思路拉回到正轨。

仔细想想,EditText 有焦点的时候,光标量,线也亮。所以我从 EditText 的 focus 入手考虑,有焦点的时候弹出软键盘,没焦点的时候,隐藏软键盘。

我尝试了 EditText 的 clearFocus 和 其他 View requestFocus 属性来达到焦点变换的目的使 EditText 失去焦点从而让光标消失,但是这俩种办法都没有什么用,同样,我给其他 View 设置 onClickListener 同样没有达到我想要的效果。不过最终有两个属性帮助我解决了这个问题。请继续看:

        et1.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    im.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
                }
            }
        });

我给我的 EditText 加了如上代码,点击 EditText 弹出软键盘,然后点击了 EditText 之外的空白区域,没反应。再点击一下 Button,软键盘还是没有收起。
(没有收起来就对了)
因为无论是界面中的空白区域,还是 button 它们都没能力去抢夺走 EditText 的焦点,这个时候我给界面的根布局设置两个属性达到了目的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.blog.melo.buzzerbeater.MainActivity"
    tools:showIn="@layout/activity_main">

    <EditText
        android:id="@+id/et1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="et1" />

    <EditText
        android:id="@+id/et2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="et2" />

android:clickable="true" android:focusableInTouchMode="true"
没错就是这两个属性,无论是设置给根布局,还是 button,都能做到将焦点获取,并隐藏软键盘的效果。到目前为止,我们的 bug 算是解决了。

另外多说一个我遇到的坑。当我的编译版本为 23.0.0 的时候,我给最外层的 CoordinatorLayout 设置 clickablefocusableInTouchMode 属性的时候,程序直接崩溃了,去 SO 上搜了搜,换了编译版本为 23.0.4 之后,崩溃解决了,但是 CoordinatorLayout 依然无法获取焦点,我退而求其次,给我的 content_main 布局设置属性,此时生效。为了让我点击 Toolbar 之后,软键盘也消失,我又给 Toobar 的布局设置了这俩属性,终于达到了我要的效果。(非常不优雅的解决办法)

继续我们的寻找最优解之路,下面来看看第二个方法:

    public void setupUI(View view) {

        if (!(view instanceof EditText)) {
            view.setOnTouchListener(new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    hideSoftKeyboard(MainActivity.this);
                    return false;
                }
            });
        }

        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                View innerView = ((ViewGroup) view).getChildAt(i);
                setupUI(innerView);
            }
        }
    }

    public static void hideSoftKeyboard(Activity activity) {
        InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
    }

新增两个方法,给整个 View 树中所有的 View 设置 onTouchListener ,然后我们把 RootView 传进去:

        LinearLayout contentMain = (LinearLayout) findViewById(R.id.content_main);

        setupUI(contentMain);

先来说说这个方法的问题,我们给界面中所有的 View 设置的触摸监听,当我触摸的不是 EditText 的时候,把软键盘隐藏。如果我没有给其它 view 设置android:clickable="true" android:focusableInTouchMode="true"属性,那么焦点依然是在 EditText 上的,光标自然也不会消失了。

(在魅族手机上测试光标居然消失了...原因不得而知,我突然间觉得第一次国产的 rom 帮了我优化,但是 nexus 上是不行的,总之还是需要我想办法去处理。)

既然有了第二种办法,回过头来看看第一种方法,第一种解决方法的问题在哪里呢?相信你也能感知到,如果我的界面复杂,难道我要给每一个 View 设置可点击的属性来达到目的吗?而且我需要给每个 EditText 都设置 onFocusChangeListener,无疑会增加代码量,让我们的代码可读性变差,并且极有可能出错。

前两种方法结合起来使用,确实可以解决大部分问题出现的场景了。我相信如果你对目前这解决方案心存不满的理由一定是:我需要对每个 EditText 都处理,或者对每个根布局都进行处理。这显然不够合理,所以来看下面这个方法。

创建一个 BaseActivity,完整代码如下:

public class BaseActivity extends AppCompatActivity {

    @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);
    }

    /**
     * 根据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;
    }

    /**
     * 多种隐藏软件盘方法的其中一种
     *
     * @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);
        }
    }

}

目前的第三个解决方案是在 Activity 的 dispatchTouchEvent 方法中进行一系列判断,此刻我点击界面中的任何非 EditText 部分,软键盘都会收起来,并且我不需要在具体的对每一个 EditText 进行处理。

研究到这里心情好了很多,理清思路,目前我们还差最后一步了,目前实现了软键盘的隐藏,只要再把焦点给其他 View,EditText 的光标自然就消失了。相信你肯定没忘记,此刻需要给 View 设置 android:clickable="true" android:focusableInTouchMode="true" 属性

目前这种情况足够解决大部分问题,而我确实遇到了一个无法解决的。因为我需要对一个 TextView 的 enable 属性进行动态的管理,这个属性明显影响到了 clickablefocusableInTouchMode 属性,这个时候怎么办呢?看起来我只能对这种场景进行特殊处理了:

当我点击这个 TextView 的时候,我使用 et.setFocusable(false) ,移除它的焦点来消除 EditText 的光标,然后:

        et.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                et.setFocusableInTouchMode(true);
                return false;
            }
        });

让 EditText 在触摸事件中,再次获得焦点。

OK,研究到了这里的解决方案基本上我可以接受了。如果有优雅的解决办法,欢迎来骚扰我~

有些朋友说,我想监听系统软键盘的事件,通过它的弹出或者收起来做某些我的需求,可是系统并没有提供出来相应的办法,应该怎么解决?

这里推荐一个网上我认为是最好的方案:

    /**
     * 监听软键盘事件
     *
     * @param rootView
     * @return
     */
    private boolean isKeyboardShown(View rootView) {
        final int softKeyboardHeight = 100;
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        int heightDiff = rootView.getBottom() - r.bottom;
        return heightDiff > softKeyboardHeight * dm.density;
    }

其原理是通过监听可见根布局的尺寸大小,来判断是否认为系统弹出了软键盘。

重写根布局的 View ,在 onMeasure 中使用这个方法。

public class CommonLinearLayout extends LinearLayout {
    public CommonLinearLayout(Context context) {
        this(context, null);
    }

    public CommonLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CommonLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (isKeyboardShown(this)) {
            Log.e("CommonLinearLayout","show");
        }else {
            Log.e("CommonLinearLayout","hide");
        }
    }

    /**
     * 监听软键盘事件
     *
     * @param rootView
     * @return
     */
    private boolean isKeyboardShown(View rootView) {
        final int softKeyboardHeight = 100;
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        int heightDiff = rootView.getBottom() - r.bottom;
        return heightDiff > softKeyboardHeight * dm.density;
    }

}

测试结果:

测试结果

可以看到系统正确判断了软键盘的弹起和隐藏。可以根据它来做你想要的操作。

长舒一口气,本文到这里也要结束了,这就是一次我对软键盘和 EditText 的研究,如果有更好的办法,欢迎告知哦~

祝大家周末愉快,天冷添衣服。

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

推荐阅读更多精彩内容

  • android 中关于键盘和焦点的问题,有时候处理不好,真的让人抓狂,昨天在实现需求的时候被键盘和焦点的问题搞得难...
    orzangleli阅读 4,151评论 1 5
  • 首先键盘是什么,键盘其实是一个系统的dialog。当她出现的时候肯定会对屏幕的尺寸造成影响。所以屏幕会重绘什么啊,...
    OnPush阅读 2,290评论 0 1
  • 深秋了,天气渐渐地凉了,树上的叶子也一片片地飘落到地上,给人一种萧瑟之感。在这样的季节里,能给人安慰的,或许只有菊...
    简JN阅读 422评论 12 22
  • 没有技巧才是最大的实力。 规律、技巧、捷径,说到底都是想要快速达成目标的手段,是双刃剑; 一方面,能快速看到成效自...
    珞小六阅读 177评论 0 0
  • 秋天里我好想你落叶为我打开心扉想你的思念让文字有了阳光的味道你爱蓝天我也是只是你不会懂我对你的思念无法用言语修饰只...
    石川河女神阅读 174评论 0 0