Android Kotlin 監(jiān)聽軟鍵盤彈出與關(guān)閉

前言

眾所周知,google是沒有為android提供官方的API來監(jiān)聽軟鍵盤的彈出與關(guān)閉的,通俗的做法都是監(jiān)聽Activity這個(gè)window的布局變化來判斷是否彈出/關(guān)閉軟鍵盤

代碼實(shí)現(xiàn)

需要說明的是,這兒并不使用ViewTreeObserver.OnGlobalLayoutListener來實(shí)現(xiàn)對布局的監(jiān)聽,而是會開一個(gè)子線程定時(shí)檢查,因?yàn)樵趯?shí)際生產(chǎn)中發(fā)現(xiàn),onGlobalLayout()的刷新時(shí)間是不確定的,跟布局的復(fù)雜程度有關(guān),有的時(shí)候可能要2/3秒才會回調(diào)一次,靠這個(gè)來監(jiān)聽window的變化來判斷是否彈出/關(guān)閉小鍵盤都涼成什么樣了。
這兒會使用RxJava來開一個(gè)子線程,見Android Kotlin 基于RxJava的簡單封裝
弄一個(gè)looper來不斷循壞檢查window的變化,發(fā)生變化時(shí)便會吐出一個(gè)事件。

IKeyBoardCallback
interface IKeyBoardCallback {

    /**
     * 當(dāng)鍵盤顯示時(shí)回調(diào)
     */
    fun onKeyBoardShow()

    /**
     * 當(dāng)鍵盤隱藏時(shí)回調(diào)
     */
    fun onKeyBoardHidden()
}
GlobalLayoutListenerTask
class GlobalLayoutListenerTask(private val activity: Activity) : SingleTask<Unit>(){

    private val iKeyBoardCallbackList = mutableListOf<IKeyBoardCallback>()
    private var status = NONE
    private val interval = 100L

    /** 全屏?xí)r的高度 */
    private var fullScreenHeight = -1
    /** 狀態(tài)欄高度 */
    private var statusBarHeight = -1

    override fun onTaskRun() {
        while (isRunning){
            try {
                //獲取可視范圍
                val rect = Rect()
                activity.window.decorView.getWindowVisibleDisplayFrame(rect)
                //獲取屏幕高度
                val screenHeight = getFullScreenHeight(activity)
                //獲取狀態(tài)欄高度
                val statueHeight = getStatueBarHeight(activity)
                //獲取被遮擋高度(鍵盤高度)(屏幕高度-狀態(tài)欄高度-可視范圍)
                val keyBoardHeight: Int = screenHeight - statusBarHeight - rect.height()
                //顯示或者隱藏
                val isKeyBoardShow = keyBoardHeight >= screenHeight / 3
                //當(dāng)首次或者和之前的狀態(tài)不一致的時(shí)候會回調(diào),反之不回調(diào)(用于當(dāng)狀態(tài)變化后才回調(diào),防止多次調(diào)用)
                if (status == NONE || (isKeyBoardShow && status == HIDDEN) || (!isKeyBoardShow && status == SHOW)) {
                    if (isKeyBoardShow) {
                        status = SHOW
                        dispatchKeyBoardShowEvent()
                    } else {
                        status = HIDDEN
                        dispatchKeyBoardHiddenEvent()
                    }
                }
                Thread.sleep(interval)
            } catch (e: Exception){
                e.printStackTrace()
            }
        }
    }

    /**
     * 用于獲取全屏?xí)r的整體高度
     *
     * @return 屏幕高度
     */
    private fun getFullScreenHeight(activity: Activity): Int {
        if (fullScreenHeight == -1){
            val vm = activity.windowManager
            fullScreenHeight = vm.defaultDisplay.height
        }
        return fullScreenHeight
    }

    /**
     * 用于獲取狀態(tài)欄高度
     *
     * @return 狀態(tài)欄高度
     */
    private fun getStatueBarHeight(activity: Activity): Int {
        if (statusBarHeight == -1){
            val res = activity.resources
            val resId = res.getIdentifier("status_bar_height", "dimen", "android")
            statusBarHeight =  res.getDimensionPixelSize(resId)
        }
        return statusBarHeight;
    }

    /**
     * 添加監(jiān)聽回調(diào)
     *
     * @param callback 監(jiān)聽的回調(diào)類
     */
    fun addCallBack(callback: Any?){
        if (callback is IKeyBoardCallback)  iKeyBoardCallbackList.add(callback)
    }

    /**
     * 移除監(jiān)聽回調(diào)
     *
     * @param callback 監(jiān)聽的回調(diào)類
     */
    fun removeCallback(callback: Any?){
        if (callback is IKeyBoardCallback)  iKeyBoardCallbackList.remove(callback)
    }

    /**
     * 分發(fā)隱藏事件
     */
    private fun dispatchKeyBoardHiddenEvent(){
        for (callback in iKeyBoardCallbackList){
            callback.onKeyBoardHidden()
        }
    }

    /**
     * 分發(fā)顯示事件
     */
    private fun dispatchKeyBoardShowEvent(){
        for (callback in iKeyBoardCallbackList){
            callback.onKeyBoardShow()
        }
    }

    /**
     * 判斷是不是沒有監(jiān)聽回調(diào)
     *
     * @return true:空 false:不空
     */
    fun isEmpty(): Boolean = iKeyBoardCallbackList.isEmpty()

    companion object {
        private const val NONE = 0;
        private const val SHOW = 1;
        private const val HIDDEN = 2;
    }
}
KeyBoardEventBus
object KeyBoardEventBus {

    private val taskCache: Hashtable<Any,GlobalLayoutListenerTask> = Hashtable()

    /**
     * 用于注冊鍵盤監(jiān)聽,此方法適用于 View、Dialog、Fragement、FragementActivity、Activity
     *
     * @param obj 需要監(jiān)聽的類()
     */
    fun register(obj: Any?){
        val activity = getActivity(obj)
        if (activity == null){
            debug("register時(shí)獲取activity失?。?)
            return
        }
        register(activity, obj)
    }

    /**
     * 此方法區(qū)別于 {@link #register(Object)} ,之前的方法會限制注冊的類型,當(dāng)前的不會限制類型
     *
     * @param activity 宿主activity
     * @param obj   監(jiān)聽的類
     */
    fun register(activity: Activity, obj: Any?){
        if (obj == null) {
            debug("object為null!")
            return
        }
        var task = taskCache[activity]
        if (task == null){
            task = GlobalLayoutListenerTask(activity)
        }
        task.addCallBack(obj)
        if (!task.isEmpty()) task.start()
        taskCache[activity] = task
    }

    /**
     * 反注冊
     *
     * @param obj 取消監(jiān)聽的類
     */
    fun unRegister(obj: Any?){
        //獲取失敗則直接停止,反之進(jìn)行反注冊
        val activity = getActivity(obj)
        if (activity == null){
            debug("unRegister時(shí)獲取activity失敗")
            return
        }
        unRegister(activity, obj)
    }

    /**
     * 反注冊
     *
     * @param activity 宿主activity
     * @param obj 監(jiān)聽的類
     */
    fun unRegister(activity: Activity, obj: Any?){
        if ( obj == null) {
            debug("activity或object為null!")
            return
        }
        val task = taskCache[activity] ?: return
        task.removeCallback(obj)
        if (task.isEmpty()){
            task.cancel()
            taskCache.remove(task)
        }
    }

    /**
     * 獲取對應(yīng)View、Dialog、Fragment、FragmentActivity、Activity
     * (如果Object為null或者不是支持的類型則返回null)
     *
     * @param obj 需要獲取的類
     * @return 返回對應(yīng)的activity
     */
    private fun getActivity(obj: Any?): Activity?{
        if (obj == null) return null

        return when(obj){
            is View -> obj.context as Activity
            is Dialog -> obj.context as Activity
            is Fragment -> obj.activity
            is FragmentActivity -> obj
            is Activity -> obj
            else -> null
        }
    }

    /**
     * 用于打印信息
     *
     * @param msg 待打印的內(nèi)容
     */
    private fun debug(msg: String){
        Log.e("KeyBoardEventBus",msg)
    }
}

用法

postUI見Android Kotlin 代碼筆記,全局的UI線程回調(diào)函數(shù)(基于擴(kuò)展函數(shù))
用來回調(diào)到UI線程彈Toast的

class MainActivity : AppCompatActivity(),IKeyBoardCallback {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        KeyBoardEventBus.register(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        KeyBoardEventBus.unRegister(this)
    }

    override fun onKeyBoardShow() {
        postUI {
            Toast.makeText(this, "鍵盤顯示",Toast.LENGTH_SHORT).show()
        }
    }

    override fun onKeyBoardHidden() {
        postUI {
            Toast.makeText(this, "鍵盤隱藏",Toast.LENGTH_SHORT).show()
        }
    }
}

搞定收工

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

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