Android Kotlin 监听软键盘弹出与关闭

前言

众所周知,google是没有为android提供官方的API来监听软键盘的弹出与关闭的,通俗的做法都是监听Activity这个window的布局变化来判断是否弹出/关闭软键盘

代码实现

需要说明的是,这儿并不使用ViewTreeObserver.OnGlobalLayoutListener来实现对布局的监听,而是会开一个子线程定时检查,因为在实际生产中发现,onGlobalLayout()的刷新时间是不确定的,跟布局的复杂程度有关,有的时候可能要2/3秒才会回调一次,靠这个来监听window的变化来判断是否弹出/关闭小键盘都凉成什么样了。
这儿会使用RxJava来开一个子线程,见Android Kotlin 基于RxJava的简单封装,
弄一个looper来不断循坏检查window的变化,发生变化时便会吐出一个事件。

IKeyBoardCallback
interface IKeyBoardCallback {

    /**
     * 当键盘显示时回调
     */
    fun onKeyBoardShow()

    /**
     * 当键盘隐藏时回调
     */
    fun onKeyBoardHidden()
}
GlobalLayoutListenerTask
class GlobalLayoutListenerTask(private val activity: Activity) : SingleTask(){

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

    /** 全屏时的高度 */
    private var fullScreenHeight = -1
    /** 状态栏高度 */
    private var statusBarHeight = -1

    override fun onTaskRun() {
        while (isRunning){
            try {
                //获取可视范围
                val rect = Rect()
                activity.window.decorView.getWindowVisibleDisplayFrame(rect)
                //获取屏幕高度
                val screenHeight = getFullScreenHeight(activity)
                //获取状态栏高度
                val statueHeight = getStatueBarHeight(activity)
                //获取被遮挡高度(键盘高度)(屏幕高度-状态栏高度-可视范围)
                val keyBoardHeight: Int = screenHeight - statusBarHeight - rect.height()
                //显示或者隐藏
                val isKeyBoardShow = keyBoardHeight >= screenHeight / 3
                //当首次或者和之前的状态不一致的时候会回调,反之不回调(用于当状态变化后才回调,防止多次调用)
                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()
            }
        }
    }

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

    /**
     * 用于获取状态栏高度
     *
     * @return 状态栏高度
     */
    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;
    }

    /**
     * 添加监听回调
     *
     * @param callback 监听的回调类
     */
    fun addCallBack(callback: Any?){
        if (callback is IKeyBoardCallback)  iKeyBoardCallbackList.add(callback)
    }

    /**
     * 移除监听回调
     *
     * @param callback 监听的回调类
     */
    fun removeCallback(callback: Any?){
        if (callback is IKeyBoardCallback)  iKeyBoardCallbackList.remove(callback)
    }

    /**
     * 分发隐藏事件
     */
    private fun dispatchKeyBoardHiddenEvent(){
        for (callback in iKeyBoardCallbackList){
            callback.onKeyBoardHidden()
        }
    }

    /**
     * 分发显示事件
     */
    private fun dispatchKeyBoardShowEvent(){
        for (callback in iKeyBoardCallbackList){
            callback.onKeyBoardShow()
        }
    }

    /**
     * 判断是不是没有监听回调
     *
     * @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 = Hashtable()

    /**
     * 用于注册键盘监听,此方法适用于 View、Dialog、Fragement、FragementActivity、Activity
     *
     * @param obj 需要监听的类()
     */
    fun register(obj: Any?){
        val activity = getActivity(obj)
        if (activity == null){
            debug("register时获取activity失败!")
            return
        }
        register(activity, obj)
    }

    /**
     * 此方法区别于 {@link #register(Object)} ,之前的方法会限制注册的类型,当前的不会限制类型
     *
     * @param activity 宿主activity
     * @param obj   监听的类
     */
    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 取消监听的类
     */
    fun unRegister(obj: Any?){
        //获取失败则直接停止,反之进行反注册
        val activity = getActivity(obj)
        if (activity == null){
            debug("unRegister时获取activity失败")
            return
        }
        unRegister(activity, obj)
    }

    /**
     * 反注册
     *
     * @param activity 宿主activity
     * @param obj 监听的类
     */
    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)
        }
    }

    /**
     * 获取对应View、Dialog、Fragment、FragmentActivity、Activity
     * (如果Object为null或者不是支持的类型则返回null)
     *
     * @param obj 需要获取的类
     * @return 返回对应的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 待打印的内容
     */
    private fun debug(msg: String){
        Log.e("KeyBoardEventBus",msg)
    }
}

用法

postUI见Android Kotlin 代码笔记,全局的UI线程回调函数(基于扩展函数)
用来回调到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()
        }
    }
}

搞定收工

你可能感兴趣的:(Android Kotlin 监听软键盘弹出与关闭)