android 輸入法顯示隱藏監(jiān)聽

先看使用代碼,非常簡單,就是弄一個回調(diào)處理就行,其他的不用關(guān)心

        SoftKeyBoardListener.setListener(this,object :SoftKeyBoardListener.OnSoftKeyBoardChangeListener{
            override fun keyBoardShow(height: Int) {//height是鍵盤的高度
                
            }

            override fun keyBoardHide() {
                
            }
        })

工具類如下

import android.app.Activity;
import android.arch.lifecycle.GenericLifecycleObserver;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;

public class SoftKeyBoardListener {
    public interface OnSoftKeyBoardChangeListener {
        void keyBoardShow(int height);

        void keyBoardHide();
    }

    private View rootView;//activity的根視圖
    private int screenBottom;//紀(jì)錄根視圖的顯示高度
    private OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener;
    boolean isShow = false;//軟鍵盤是否顯示
    private ViewTreeObserver.OnGlobalLayoutListener listener;

    private SoftKeyBoardListener(Activity activity) {
        int state = WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE & activity.getWindow().getAttributes().softInputMode;
        switch (state) {
            case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
            case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
                isShow = true;
                break;
        }
        //獲取activity的根視圖
        rootView = activity.getWindow().getDecorView();
        screenBottom = activity.getWindowManager().getDefaultDisplay().getHeight();
        //監(jiān)聽視圖樹中全局布局發(fā)生改變或者視圖樹中的某個視圖的可視狀態(tài)發(fā)生改變
        listener = new ViewTreeObserver.OnGlobalLayoutListener() {

            @Override

            public void onGlobalLayout() {

                //獲取當(dāng)前根視圖在屏幕上顯示的大小
                Rect r = new Rect();

                rootView.getWindowVisibleDisplayFrame(r);

                System.out.println("rect============" + isShow + "===" + r.toShortString() + "===" + screenBottom);
                if (!isShow && screenBottom > r.bottom) {
                    isShow = true;
                    if (onSoftKeyBoardChangeListener != null) {
                        onSoftKeyBoardChangeListener.keyBoardShow(screenBottom - r.bottom);
                    }
                    return;
                }

                if (isShow && r.bottom >= screenBottom) {
                    isShow = false;
                    if (onSoftKeyBoardChangeListener != null) {
                        onSoftKeyBoardChangeListener.keyBoardHide();
                    }
                    return;
                }

            }

        };
        rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
        addLifeObServer(activity);
    }

    private void setOnSoftKeyBoardChangeListener(OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {

        this.onSoftKeyBoardChangeListener = onSoftKeyBoardChangeListener;

    }

    public static void setListener(Activity activity, OnSoftKeyBoardChangeListener onSoftKeyBoardChangeListener) {

        SoftKeyBoardListener softKeyBoardListener = new SoftKeyBoardListener(activity);

        softKeyBoardListener.setOnSoftKeyBoardChangeListener(onSoftKeyBoardChangeListener);

    }

    public void addLifeObServer(Activity activity) {
        if (activity instanceof LifecycleOwner) {
            LifecycleOwner owner = (LifecycleOwner) activity;
            owner.getLifecycle().addObserver(new GenericLifecycleObserver() {
                @Override
                public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        if (rootView != null)
                            rootView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
                    }
                }
            });
        }
    }


    public static void closeKeybord(EditText mEditText, Context mContext) {

        InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);

        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);

        mEditText.setFocusable(false);

    }
}

工具類簡單說明

核心方法就是rootView.getWindowVisibleDisplayFrame(r)
獲取可見的view的布局范圍,然后根據(jù)屏幕計算出軟鍵盤的高度
isShow這個值的作用,軟鍵盤彈出以后,比如兩個輸入框,一個是文本,一個是數(shù)字,2個切換的時候軟鍵盤高度是會發(fā)生變化的,可這時候不需要回調(diào)鍵盤是否顯示的,所以顯示隱藏的回調(diào)不會多次執(zhí)行,另外也能處理用戶如果默認(rèn)軟鍵盤要求彈出的情況。

輸入法的模式問題

先看下輸入法支持的模式,如下,其實可以分為兩類,一類是state,一類是adjust

@IntDef(flag = true, value = {
                SOFT_INPUT_STATE_UNSPECIFIED,//0
                SOFT_INPUT_STATE_UNCHANGED,//1
                SOFT_INPUT_STATE_HIDDEN,//2
                SOFT_INPUT_STATE_ALWAYS_HIDDEN,//3
                SOFT_INPUT_STATE_VISIBLE,//4
                SOFT_INPUT_STATE_ALWAYS_VISIBLE,//5
                SOFT_INPUT_ADJUST_UNSPECIFIED,//0x00
                SOFT_INPUT_ADJUST_RESIZE,//0x10
                SOFT_INPUT_ADJUST_PAN,//0x20
                SOFT_INPUT_ADJUST_NOTHING,//0x30
                SOFT_INPUT_IS_FORWARD_NAVIGATION,//0x100
        })

state從0到5,而adjust是十六進(jìn)制的十位從0到3.這樣的目的,到時候通過一個mask可以取出state和adjust
比如 android:windowSoftInputMode="stateVisible|adjustResize"
拿到的softinputmode=4+16也就是20
這里有2個mask
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

int state=WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE&mode;
進(jìn)行與操作以后,高位就沒了,就剩下低位了,也就剩下個4了。
同理int adjust=WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST&mode;
低位就沒了,就剩下個十六進(jìn)制的高位1了,也就是0x10,也就是拿到了adjust為16

這種mask在MotionEvent里是一樣的,可以去參考源碼

清單文件里如下的代碼

android:windowSoftInputMode="stateVisible|stateAlwaysVisible" 

這種其實不合理,正確的寫法,state和adjust應(yīng)該每樣最多一個,或者不寫。
那么這樣寫了也不會錯,反正就是你寫的2個值它進(jìn)行或操作,最后得到的結(jié)果,如果在1到5之間,那么state自然有效,如果不在,那就無效的。
上邊2個就是4|5 或操作,也就是二進(jìn)制100和101,最后結(jié)果還是101

題外話

本人測試沒發(fā)現(xiàn)有啥問題,如果有人使用中發(fā)現(xiàn)有問題,麻煩告知一下,我好處理。
這個類,也是很久以前不知道哪里找的,做了下修改

如何點擊界面其他任何地方隱藏輸入法

首先隱藏輸入法的方法如下

    fun closeKeybord(mEditText: View?, mContext: Context) {
        val imm = mContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(mEditText?.windowToken, 0)
    }

然后我們需要在activity的dispatchTouchEvent方法里處理,因為這個是所有觸摸事件的起點

1.比較簡單,代碼少的一種方法
點擊的時候判斷屏幕展示的大小,如果獲取的高度比屏幕高度小,說明這時候輸入法是彈出的,那么我們把輸入法隱藏即可
缺點:點擊另外一個edittextview的時候,輸入法也會先隱藏,再彈出。

    //獲取當(dāng)前根視圖在屏幕上顯示的大小
    val r = Rect()
    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        if(ev.actionMasked==MotionEvent.ACTION_DOWN){
            window.decorView.getWindowVisibleDisplayFrame(r)
            if(r.bottom<windowManager.defaultDisplay.height){
                closeKeybord(currentFocus,this)
            }

        }
        return super.dispatchTouchEvent(ev)
    }

2.代碼多點,就是為了解決上邊的缺點
我們循環(huán)所有的view,找到手指觸摸的是否是edittextview,如果是的話,不做任何處理,如果不是,那么隱藏輸入法

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {

        if(ev.actionMasked==MotionEvent.ACTION_DOWN){
            val result=isClickEditText(ev.rawX,ev.rawY)
            if(!result){
                closeKeybord(currentFocus,this)
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    val childLocation=IntArray(2)
    private fun checkLocation(root:ViewGroup,x:Float,y:Float):Boolean{
        repeat(root.childCount){
            val child=root.getChildAt(it)
            if(child is ViewGroup){
                val result=checkLocation(child,x,y)
                if(result){
                    return true
                }
            }else{
                child.getLocationOnScreen(childLocation)
            if((child is EditText)&&x>childLocation[0]&&x<childLocation[0]+child.width&&y>childLocation[1]&&y<childLocation[1]+child.height){
                println("get child=======$child")
                return true
            }
            }
        }
        return false
    }

最后復(fù)習(xí)下幾個用到的方法

  1. getLocationOnScreen和getLocationInWindow的區(qū)別
    看下第一個的源碼,就知道了
    對于普通的activity來講,這兩個是沒區(qū)別的,因為window的left和top都是0.
    不過對于如果是dialog的話,window的left和top就不是0了,兩者就有區(qū)別了。
    public void getLocationOnScreen(@Size(2) int[] outLocation) {
        getLocationInWindow(outLocation);

        final AttachInfo info = mAttachInfo;
        if (info != null) {
            outLocation[0] += info.mWindowLeft;
            outLocation[1] += info.mWindowTop;
        }
    }
  1. getX ,getRawX的區(qū)別
    getx:這個是相對于view本身距離左邊的距離,舉個列子,如果event是一個button的touch事件,那么這個x就是距離button左邊界的距離, 如果這個event是activity里的touch事件,那么這個x就是整個窗體左邊距離拉。

getRawX: 這個始終是觸摸的地方距離整個屏幕左側(cè)的距離。

bug

昨天在看到有人說在全屏模式下,監(jiān)聽不到,我趕緊測試了下,果然全屏模式下,輸入法隱藏顯示沒任何反應(yīng)

  1. 頁面有非全屏切換為全屏以后,可以看到下邊的日志
rootView.getWindowVisibleDisplayFrame(r);
//這個rect打印的結(jié)果[-10000,-10000][10000,10000]

我們利用的就是OnGlobalLayoutListener 可全屏以后會發(fā)現(xiàn),這個監(jiān)聽失效了。所以回調(diào)也就沒了,就算能監(jiān)聽到,上邊也看到了,結(jié)果都成了1萬了,數(shù)據(jù)都無效了。

不知道咋下手,網(wǎng)上看下別人咋弄的
https://github.com/pqpo/InputMethodHolder

測試


InputMethodManagerService研究
https://blog.csdn.net/ITleaks/article/details/27398453

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容