先看使用代码,非常简单,就是弄一个回调处理就行,其他的不用关心
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;//纪录根视图的显示高度
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();
//监听视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变
listener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//获取当前根视图在屏幕上显示的大小
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的布局范围,然后根据屏幕计算出软键盘的高度
isShow这个值的作用,软键盘弹出以后,比如两个输入框,一个是文本,一个是数字,2个切换的时候软键盘高度是会发生变化的,可这时候不需要回调键盘是否显示的,所以显示隐藏的回调不会多次执行,另外也能处理用户如果默认软键盘要求弹出的情况。
输入法的模式问题
先看下输入法支持的模式,如下,其实可以分为两类,一类是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是十六进制的十位从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;
进行与操作以后,高位就没了,就剩下低位了,也就剩下个4了。
同理int adjust=WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST&mode;
低位就没了,就剩下个十六进制的高位1了,也就是0x10,也就是拿到了adjust为16
这种mask在MotionEvent里是一样的,可以去参考源码
清单文件里如下的代码
android:windowSoftInputMode="stateVisible|stateAlwaysVisible"
这种其实不合理,正确的写法,state和adjust应该每样最多一个,或者不写。
那么这样写了也不会错,反正就是你写的2个值它进行或操作,最后得到的结果,如果在1到5之间,那么state自然有效,如果不在,那就无效的。
上边2个就是4|5 或操作,也就是二进制100和101,最后结果还是101
题外话
本人测试没发现有啥问题,如果有人使用中发现有问题,麻烦告知一下,我好处理。
这个类,也是很久以前不知道哪里找的,做了下修改
如何点击界面其他任何地方隐藏输入法
首先隐藏输入法的方法如下
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的时候,输入法也会先隐藏,再弹出。
//获取当前根视图在屏幕上显示的大小
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.代码多点,就是为了解决上边的缺点
我们循环所有的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
}
最后复习下几个用到的方法
- getLocationOnScreen和getLocationInWindow的区别
看下第一个的源码,就知道了
对于普通的activity来讲,这两个是没区别的,因为window的left和top都是0.
不过对于如果是dialog的话,window的left和top就不是0了,两者就有区别了。
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的区别
getx:这个是相对于view本身距离左边的距离,举个列子,如果event是一个button的touch事件,那么这个x就是距离button左边界的距离, 如果这个event是activity里的touch事件,那么这个x就是整个窗体左边距离拉。
getRawX: 这个始终是触摸的地方距离整个屏幕左侧的距离。
bug
昨天在看到有人说在全屏模式下,监听不到,我赶紧测试了下,果然全屏模式下,输入法隐藏显示没任何反应
- 页面有非全屏切换为全屏以后,可以看到下边的日志
rootView.getWindowVisibleDisplayFrame(r);
//这个rect打印的结果[-10000,-10000][10000,10000]
我们利用的就是OnGlobalLayoutListener 可全屏以后会发现,这个监听失效了。所以回调也就没了,就算能监听到,上边也看到了,结果都成了1万了,数据都无效了。
不知道咋下手,网上看下别人咋弄的
https://github.com/pqpo/InputMethodHolder
测试
InputMethodManagerService研究
https://blog.csdn.net/ITleaks/article/details/27398453