Android 軟鍵盤隱藏尋找最優(yōu)解

Android 軟鍵盤隱藏尋找最優(yōu)解

本文原創(chuàng)芹缔,轉(zhuǎn)載請(qǐng)注明出處养匈。
歡迎關(guān)注我的 簡(jiǎn)書 哼勇,關(guān)注我的專題 Android Class 我會(huì)長(zhǎng)期堅(jiān)持為大家收錄簡(jiǎn)書上高質(zhì)量的 Android 相關(guān)博文。

寫在前面:
最近我自己的開發(fā)任務(wù)接近尾聲呕乎,提交測(cè)試之后收到了一個(gè) bug积担,這個(gè) bug 描述起來是這個(gè)樣子的:

希望當(dāng)點(diǎn)擊外部空白區(qū)域軟鍵盤隱藏的時(shí)候,EditText 的光標(biāo)也消失猬仁。

當(dāng)我看到這個(gè) bug 的時(shí)候帝璧,心里想,額...應(yīng)該不難吧湿刽,隱藏軟鍵盤大家都會(huì)的烁,那當(dāng)我隱藏軟鍵盤的時(shí)候,讓 EditText 的 Cursor 消失就不好了诈闺?
事實(shí)上解決這個(gè)問題確實(shí)不難渴庆,但是作為一個(gè)稍微有點(diǎn)追(jiao)求(qing)的程序員,其實(shí)解決這個(gè)問題雅镊,還是經(jīng)歷了一些思考過程的把曼,所以我把它整理出來,分享給大家漓穿。

先來看看這個(gè) bug 的描述:當(dāng)軟鍵盤隱藏嗤军,光標(biāo)消失。

測(cè)試的這段描述直接對(duì)我這種心思單純的程序猿造成了誤導(dǎo)晃危,因?yàn)樗苯影盐业乃悸芬搅斯鈽?biāo)的處理上:

先不說軟鍵盤了叙赚,直接看看處理 cursor 是什么效果:

et1 隱藏光標(biāo)
et2 不作處理

這個(gè) Demo 項(xiàng)目我目前有兩個(gè) EditText et1,et2僚饭,還有一個(gè)不做任何處理的 button震叮,此時(shí)我僅僅給 et1 隱藏光標(biāo) cursor,調(diào)用 et1.setCursorVisible(false)鳍鸵,可以看到上圖的效果苇瓣,et1 的光標(biāo)消失了。

head da

是啊通常我們項(xiàng)目里面的 EditText 只有一個(gè)光標(biāo)偿乖,那光標(biāo)是消失了击罪,萬(wàn)一底下有那條線呢哲嘲?不管了?

不要說再隱藏下面那條線就 ok 了媳禁,這樣一來就太復(fù)雜了眠副,說明我們思考的出發(fā)點(diǎn)有問題。好吧我們?cè)噲D將思路拉回到正軌竣稽。

仔細(xì)想想囱怕,EditText 有焦點(diǎn)的時(shí)候,光標(biāo)量毫别,線也亮娃弓。所以我從 EditText 的 focus 入手考慮,有焦點(diǎn)的時(shí)候彈出軟鍵盤岛宦,沒焦點(diǎn)的時(shí)候忘闻,隱藏軟鍵盤。

我嘗試了 EditText 的 clearFocus 和 其他 View requestFocus 屬性來達(dá)到焦點(diǎn)變換的目的使 EditText 失去焦點(diǎn)從而讓光標(biāo)消失恋博,但是這倆種辦法都沒有什么用齐佳,同樣,我給其他 View 設(shè)置 onClickListener 同樣沒有達(dá)到我想要的效果债沮。不過最終有兩個(gè)屬性幫助我解決了這個(gè)問題炼吴。請(qǐng)繼續(xù)看:

        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 加了如上代碼,點(diǎn)擊 EditText 彈出軟鍵盤疫衩,然后點(diǎn)擊了 EditText 之外的空白區(qū)域硅蹦,沒反應(yīng)。再點(diǎn)擊一下 Button闷煤,軟鍵盤還是沒有收起童芹。
(沒有收起來就對(duì)了)
因?yàn)闊o(wú)論是界面中的空白區(qū)域,還是 button 它們都沒能力去搶奪走 EditText 的焦點(diǎn)鲤拿,這個(gè)時(shí)候我給界面的根布局設(shè)置兩個(gè)屬性達(dá)到了目的:

<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"
沒錯(cuò)就是這兩個(gè)屬性假褪,無(wú)論是設(shè)置給根布局,還是 button近顷,都能做到將焦點(diǎn)獲取生音,并隱藏軟鍵盤的效果。到目前為止窒升,我們的 bug 算是解決了缀遍。

另外多說一個(gè)我遇到的坑。當(dāng)我的編譯版本為 23.0.0 的時(shí)候饱须,我給最外層的 CoordinatorLayout 設(shè)置 clickablefocusableInTouchMode 屬性的時(shí)候域醇,程序直接崩潰了,去 SO 上搜了搜,換了編譯版本為 23.0.4 之后譬挚,崩潰解決了锅铅,但是 CoordinatorLayout 依然無(wú)法獲取焦點(diǎn),我退而求其次殴瘦,給我的 content_main 布局設(shè)置屬性狠角,此時(shí)生效号杠。為了讓我點(diǎn)擊 Toolbar 之后蚪腋,軟鍵盤也消失,我又給 Toobar 的布局設(shè)置了這倆屬性姨蟋,終于達(dá)到了我要的效果屉凯。(非常不優(yōu)雅的解決辦法)

繼續(xù)我們的尋找最優(yōu)解之路,下面來看看第二個(gè)方法:

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

新增兩個(gè)方法眼溶,給整個(gè) View 樹中所有的 View 設(shè)置 onTouchListener 悠砚,然后我們把 RootView 傳進(jìn)去:

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

        setupUI(contentMain);

先來說說這個(gè)方法的問題,我們給界面中所有的 View 設(shè)置的觸摸監(jiān)聽堂飞,當(dāng)我觸摸的不是 EditText 的時(shí)候灌旧,把軟鍵盤隱藏。如果我沒有給其它 view 設(shè)置android:clickable="true" android:focusableInTouchMode="true"屬性绰筛,那么焦點(diǎn)依然是在 EditText 上的枢泰,光標(biāo)自然也不會(huì)消失了。

(在魅族手機(jī)上測(cè)試光標(biāo)居然消失了...原因不得而知铝噩,我突然間覺得第一次國(guó)產(chǎn)的 rom 幫了我優(yōu)化衡蚂,但是 nexus 上是不行的,總之還是需要我想辦法去處理骏庸。)

既然有了第二種辦法毛甲,回過頭來看看第一種方法,第一種解決方法的問題在哪里呢具被?相信你也能感知到玻募,如果我的界面復(fù)雜,難道我要給每一個(gè) View 設(shè)置可點(diǎn)擊的屬性來達(dá)到目的嗎一姿?而且我需要給每個(gè) EditText 都設(shè)置 onFocusChangeListener补箍,無(wú)疑會(huì)增加代碼量,讓我們的代碼可讀性變差啸蜜,并且極有可能出錯(cuò)坑雅。

前兩種方法結(jié)合起來使用,確實(shí)可以解決大部分問題出現(xiàn)的場(chǎng)景了衬横。我相信如果你對(duì)目前這解決方案心存不滿的理由一定是:我需要對(duì)每個(gè) EditText 都處理裹粤,或者對(duì)每個(gè)根布局都進(jìn)行處理。這顯然不夠合理,所以來看下面這個(gè)方法遥诉。

創(chuàng)建一個(gè) BaseActivity拇泣,完整代碼如下:

public class BaseActivity extends AppCompatActivity {

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 獲得當(dāng)前得到焦點(diǎn)的View,一般情況下就是EditText(特殊情況就是軌跡求或者實(shí)體案件會(huì)移動(dòng)焦點(diǎn))
            View v = getCurrentFocus();
            if (isShouldHideInput(v, ev)) {
                hideSoftInput(v.getWindowToken());
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 根據(jù)EditText所在坐標(biāo)和用戶點(diǎn)擊的坐標(biāo)相對(duì)比矮锈,來判斷是否隱藏鍵盤霉翔,因?yàn)楫?dāng)用戶點(diǎn)擊EditText時(shí)沒必要隱藏
     *
     * @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) {
                // 點(diǎn)擊EditText的事件,忽略它苞笨。
                return false;
            } else {
                return true;
            }
        }
        // 如果焦點(diǎn)不是EditText則忽略债朵,這個(gè)發(fā)生在視圖剛繪制完,第一個(gè)焦點(diǎn)不在EditView上瀑凝,和用戶用軌跡球選擇其他的焦點(diǎn)
        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);
        }
    }

}

目前的第三個(gè)解決方案是在 Activity 的 dispatchTouchEvent 方法中進(jìn)行一系列判斷序芦,此刻我點(diǎn)擊界面中的任何非 EditText 部分,軟鍵盤都會(huì)收起來粤咪,并且我不需要在具體的對(duì)每一個(gè) EditText 進(jìn)行處理谚中。

研究到這里心情好了很多,理清思路寥枝,目前我們還差最后一步了宪塔,目前實(shí)現(xiàn)了軟鍵盤的隱藏,只要再把焦點(diǎn)給其他 View囊拜,EditText 的光標(biāo)自然就消失了某筐。相信你肯定沒忘記,此刻需要給 View 設(shè)置 android:clickable="true" android:focusableInTouchMode="true" 屬性

目前這種情況足夠解決大部分問題艾疟,而我確實(shí)遇到了一個(gè)無(wú)法解決的来吩。因?yàn)槲倚枰獙?duì)一個(gè) TextView 的 enable 屬性進(jìn)行動(dòng)態(tài)的管理,這個(gè)屬性明顯影響到了 clickablefocusableInTouchMode 屬性蔽莱,這個(gè)時(shí)候怎么辦呢弟疆?看起來我只能對(duì)這種場(chǎng)景進(jìn)行特殊處理了:

當(dāng)我點(diǎn)擊這個(gè) TextView 的時(shí)候,我使用 et.setFocusable(false) 盗冷,移除它的焦點(diǎn)來消除 EditText 的光標(biāo)怠苔,然后:

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

讓 EditText 在觸摸事件中,再次獲得焦點(diǎn)仪糖。

OK柑司,研究到了這里的解決方案基本上我可以接受了。如果有優(yōu)雅的解決辦法锅劝,歡迎來騷擾我~

有些朋友說攒驰,我想監(jiān)聽系統(tǒng)軟鍵盤的事件,通過它的彈出或者收起來做某些我的需求故爵,可是系統(tǒng)并沒有提供出來相應(yīng)的辦法玻粪,應(yīng)該怎么解決?

這里推薦一個(gè)網(wǎng)上我認(rèn)為是最好的方案:

    /**
     * 監(jiān)聽軟鍵盤事件
     *
     * @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;
    }

其原理是通過監(jiān)聽可見根布局的尺寸大小,來判斷是否認(rèn)為系統(tǒng)彈出了軟鍵盤劲室。

重寫根布局的 View 伦仍,在 onMeasure 中使用這個(gè)方法。

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

    /**
     * 監(jiān)聽軟鍵盤事件
     *
     * @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;
    }

}

測(cè)試結(jié)果:

測(cè)試結(jié)果

可以看到系統(tǒng)正確判斷了軟鍵盤的彈起和隱藏很洋〕淅叮可以根據(jù)它來做你想要的操作。

長(zhǎng)舒一口氣喉磁,本文到這里也要結(jié)束了谓苟,這就是一次我對(duì)軟鍵盤和 EditText 的研究,如果有更好的辦法线定,歡迎告知哦~

祝大家周末愉快娜谊,天冷添衣服确买。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斤讥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子湾趾,更是在濱河造成了極大的恐慌芭商,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀缠,死亡現(xiàn)場(chǎng)離奇詭異铛楣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)艺普,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門簸州,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歧譬,你說我怎么就攤上這事岸浑。” “怎么了瑰步?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵矢洲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我缩焦,道長(zhǎng)读虏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任袁滥,我火速辦了婚禮盖桥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘题翻。我一直安慰自己揩徊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著靴拱,像睡著了一般垃喊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袜炕,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天本谜,我揣著相機(jī)與錄音,去河邊找鬼偎窘。 笑死乌助,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的陌知。 我是一名探鬼主播他托,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼仆葡!你這毒婦竟也來了赏参?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沿盅,失蹤者是張志新(化名)和其女友劉穎把篓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腰涧,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡韧掩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窖铡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疗锐。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖费彼,靈堂內(nèi)的尸體忽然破棺而出滑臊,到底是詐尸還是另有隱情,我是刑警寧澤敌买,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布简珠,位于F島的核電站,受9級(jí)特大地震影響虹钮,放射性物質(zhì)發(fā)生泄漏聋庵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一芙粱、第九天 我趴在偏房一處隱蔽的房頂上張望祭玉。 院中可真熱鬧,春花似錦春畔、人聲如沸脱货。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)振峻。三九已至臼疫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扣孟,已是汗流浹背烫堤。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凤价,地道東北人鸽斟。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像利诺,于是被迫代替她去往敵國(guó)和親富蓄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • android 中關(guān)于鍵盤和焦點(diǎn)的問題帐萎,有時(shí)候處理不好比伏,真的讓人抓狂胜卤,昨天在實(shí)現(xiàn)需求的時(shí)候被鍵盤和焦點(diǎn)的問題搞得難...
    orzangleli閱讀 4,575評(píng)論 1 5
  • 首先鍵盤是什么,鍵盤其實(shí)是一個(gè)系統(tǒng)的dialog赁项。當(dāng)她出現(xiàn)的時(shí)候肯定會(huì)對(duì)屏幕的尺寸造成影響葛躏。所以屏幕會(huì)重繪什么啊,...
    OnPush閱讀 2,376評(píng)論 0 1
  • 深秋了悠菜,天氣漸漸地涼了舰攒,樹上的葉子也一片片地飄落到地上,給人一種蕭瑟之感悔醋。在這樣的季節(jié)里摩窃,能給人安慰的,或許只有菊...
    簡(jiǎn)JN閱讀 467評(píng)論 12 22
  • 沒有技巧才是最大的實(shí)力芬骄。 規(guī)律猾愿、技巧、捷徑账阻,說到底都是想要快速達(dá)成目標(biāo)的手段蒂秘,是雙刃劍; 一方面淘太,能快速看到成效自...
    珞小六閱讀 199評(píng)論 0 0
  • 秋天里我好想你落葉為我打開心扉想你的思念讓文字有了陽(yáng)光的味道你愛藍(lán)天我也是只是你不會(huì)懂我對(duì)你的思念無(wú)法用言語(yǔ)修飾只...
    石川河女神閱讀 186評(píng)論 0 0