先看使用代碼,非常簡單,就是弄一個回調(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í)下幾個用到的方法
- 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;
}
}
- getX ,getRawX的區(qū)別
getx:這個是相對于view本身距離左邊的距離,舉個列子,如果event是一個button的touch事件,那么這個x就是距離button左邊界的距離, 如果這個event是activity里的touch事件,那么這個x就是整個窗體左邊距離拉。
getRawX: 這個始終是觸摸的地方距離整個屏幕左側(cè)的距離。
bug
昨天在看到有人說在全屏模式下,監(jiān)聽不到,我趕緊測試了下,果然全屏模式下,輸入法隱藏顯示沒任何反應(yīng)
- 頁面有非全屏切換為全屏以后,可以看到下邊的日志
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