前言
眾所周知,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()
}
}
}
