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 是什么效果:
這個(gè) Demo 項(xiàng)目我目前有兩個(gè) EditText et1,et2僚饭,還有一個(gè)不做任何處理的 button震叮,此時(shí)我僅僅給 et1 隱藏光標(biāo) cursor,調(diào)用 et1.setCursorVisible(false)
鳍鸵,可以看到上圖的效果苇瓣,et1 的光標(biāo)消失了。
是啊通常我們項(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è)置 clickable
和 focusableInTouchMode
屬性的時(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è)屬性明顯影響到了 clickable
和 focusableInTouchMode
屬性蔽莱,這個(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é)果:
可以看到系統(tǒng)正確判斷了軟鍵盤的彈起和隱藏很洋〕淅叮可以根據(jù)它來做你想要的操作。
長(zhǎng)舒一口氣喉磁,本文到這里也要結(jié)束了谓苟,這就是一次我對(duì)軟鍵盤和 EditText 的研究,如果有更好的辦法线定,歡迎告知哦~
祝大家周末愉快娜谊,天冷添衣服确买。