Android 键盘相关常见问题有:java
下面将对上述问题各个击破。git
etReply.setFilters(new InputFilter[]{new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (source.length() + dest.length() > COMMENT_MAX_NUM) {
Crouton.makeText(AppUtils.getString(R.string.infodetail_comment_limit), Style.ALERT).show();
}
return null;
}
}, new InputFilter.LengthFilter(COMMENT_MAX_NUM)});
复制代码
若是当前页面是Activity那么能够直接重写dispatchTouchEvent
方法。在ACTION_DOWN
事件时,判断点击的坐标是否在输入框坐标的上面,若是是那么调用隐藏键盘的方法。github
若是当前页面是Fragment,那么Fragment中增长一个dispatchTouchEvent
方法,内部逻辑同上,在Fragment所依赖的Activity代码中将dispatchTouchEvent
事件透传给Fragment的dispatchTouchEvent
,若是键盘须要隐藏,Fragment的dispatchTouchEvent
方法须要返回true
,表示消费本次全部触摸事件,再也不继续传递。api
首先须要知道一点,键盘高度不是固定的。用户使用不一样的输入法,高度可能不同;甚至有些输入法,能够直接调节输入法面板的高度。微信
没有。app
系统给咱们提供了一个页面布局变化的监听器OnGlobalLayoutListener
,这个监听器能够通知咱们布局发生改变,咱们能够在此时获取本身的高度,再经过屏幕宽度和状态栏高度等间接计算出键盘的高度。ide
那么就有一个问题,OnGlobalLayoutListener
接收到变化动做时,必定是键盘弹出或消失么?工具
不必定。布局
那为何使用OnGlobalLayoutListener
能够监听键盘的状态?测试
咱们知道每一个view的宽高变化都会致使
OnGlobalLayoutListener
的触发,可是对当前Activity的Window对象中的DecorView进行监听时,通常来讲,DecorView的尺寸不会发生变化,发生变化的主要缘由就是键盘的收起和展开,这时候加上简单的判断(变化超过某个阈值)就能够获取键盘的高度,以及是否弹起。
有没有使用OnGlobalLayoutListener
监听键盘失效的情景?
在Android7.0上,咱们可使用多任务键开启分屏/多窗口模式,当咱们开启分屏以后,调整分屏的分界线时,都会触发
DecorView
的OnGlobalLayoutListener
,可是此时键盘并未触发任何动做;并且,当咱们点击某个输入框以后,键盘在分屏模式下会变成悬浮模式,不会挤压Activity的控件,因此当键盘弹出或收起时,OnGlobalLayoutListener
不会接收到任何事件。这就致使了OnGlobalLayoutListener
彻底失效。还有一些其余的场景致使监听键盘事件失效的情景,暂时想不起来,能够在评论处补充。
在通常状况下,咱们对Activity
的PhoneWindow
中DecorView
的布局变化进行监听,通常来讲,变化值超过60dp
就能够认为是键盘弹出或收起了。并且在非全屏主题下, 键盘高度 = 屏幕高度 - 状态栏高度 - 主视图高度 - 标题栏高度, 因而咱们能够经过下面代码间接计算出键盘高度。
mDecorView = this.getActivity().getWindow().getDecorView();
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect rect = new Rect();
mDecorView.getWindowVisibleDisplayFrame(rect);
// 不能使用decorView.getHeight()获取decorview的高度,获取的高度不会发生变化
int displayHeight = rect.bottom;
if (Math.abs(displayHeight - mOldDecorViewHeight) > dp60) {
mOldDecorViewHeight =displayHeight;
int rootHeight = mRoot.getHeight();
int statusBarHeight = getStatusBarHeight();
int screenHeight = getScreenHeight();
int titleBarHeight = getTitleBarHeight();
//在非全屏模式下, 键盘高度 = 屏幕高度 - 状态栏高度 - 主视图高度 - 标题栏高度
int keyboardHeight = screenHeight - statusBarHeight - rootHeight - titleBarHeight;
Log.i("lxc", "keyboardHeight ---> " + keyboardHeight + " 键盘: " + (keyboardHeight > 0 ? "弹出" : "收起"));
}
}
};
mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
复制代码
当我点击输入框弹出和收起键盘时,会出现下面的log日志:
'''console 05-29 15:22:50.368 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 0 键盘: 收起 05-29 15:22:51.736 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 873 键盘: 弹出 05-29 15:22:52.739 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 0 键盘: 收起 05-29 15:22:53.892 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 873 键盘: 弹出 '''
在IM聊天页面,一般下面会作成相似于微信的样式(点击后可切换表情面板和键盘)。点击表情按钮,会弹出表情面板,且表情按钮变成键盘模式;再次点击键盘模式,或者点击输入框,会弹出输入框,并收起表情面板。如下篇幅均称表情面板为面板。
一般这样的页面布局是一个RecyclerView+输入区域。输入区域在RecyclerView下面,因此整个布局可使用垂直的LinearLayout。键盘模式咱们选择adjustResize
。
常规的逻辑以下:
visibility
为GONE
。VISIBLE
;收起输入法键盘;按钮图片变为键盘模式。GONE
;展开输入法键盘;按钮推盘变成表情模式。咱们按照上述思路写下关键代码:
private void initView(View root) {
mInputEt = root.findViewById(R.id.et_input);
mFaceBtn = root.findViewById(R.id.btn_face);
mPanel = root.findViewById(R.id.panel);
mFaceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPanel.getVisibility() == View.VISIBLE) {
mPanel.setVisibility(View.GONE);
mFaceBtn.setImageResource(R.drawable.emoji_download_icon);
openKeyBoard(mInputEt);
} else {
mPanel.setVisibility(View.VISIBLE);
mFaceBtn.setImageResource(R.drawable.zz_chat_reply_keyboard);
closeKeyBoard(mInputEt);
}
}
});
}
复制代码
运行看看结果:
出现了奇怪的一帧:
结论:
当屏幕中已显示键盘时,点击表情按钮弹出面板前,须要隐藏键盘,可是隐藏键盘咱们只是调用的一个远程服务(Context.INPUT_METHOD_SERVICE
),它是什么时候执行咱们没法控制(通常来讲,涉及跨进程通讯,因此执行顺序确定是在面板显示以后),因此咱们不管咱们先调用隐藏键盘api再显示表情面板,仍是先显示表情面板在调用隐藏键盘的api都会出现这一帧现象,给人的感受就是闪烁了一下。
所以,咱们隐藏和显示表情面板的时机不是点击表情按钮时就马上执行,而是须要等到输入法面板彻底显示或彻底隐藏后再进行。
这里可能涉及到一些监听键盘弹起/隐藏操做和获取键盘高度的知识。能够参见上一节小结如何获取键盘高度。咱们获取的键盘高度每次更新后直接存储在SharedPreferences
,某些应用须要从新将弹起的面板高度从新设置为与键盘高度相同,如微信就须要记录键盘的高度。但也不是每一个应用都须要面板与键盘高度一致,若是你的应用不须要能够不用看如何获取键盘高度。
若是咱们在OnGlobalLayoutListener中监听键盘的弹出或收起,并根据相应状态设置面板的隐藏或显示时会出现一些闪烁的问题(代码能够看Demo中的半解决切换键盘冲突)。由于闪烁的时间很短,因此录制gif的时候没法看到,有兴趣的能够运行Demo中半解决切换键盘冲突方案。
以键盘弹起为例,咱们的流程是这样的:
触发键盘弹起 --> OnGlobalLayoutListener接收到布局变化 --> 此时键盘已经彻底弹起 --> X --> 隐藏表情面板
这个流程中的X
指的是bug出现的时候,键盘彻底弹起时,表情键盘并无当即隐藏,而是随后隐藏的,这就致使了半解决冲突的微弱闪烁的现象。
咱们来看看ViewGroup的测量过程。ViewGroup测量时,会先去遍历测量全部的子View的尺寸,而后结合ViewGroup的测量模式计算出合适的尺寸。在咱们这个案例里,当表情面板已经展开时,若是切换到键盘,首页键盘会挤压整个布局,也就是咱们说的ViewGroup的布局,可是此时执行ViewGroup的onMeasure时,里面的表情面板仍然是可见的。而后咱们在
OnGlobalLayoutListener
的回调里将表情面板的可见性设为GONE
, 但此时已经和键盘刚展开时已经不是同一帧了,因此看到了微弱的闪烁效果。
根据上面的分析,咱们须要在键盘收起时的那一帧中,测量ViewGroup尺寸时,直接从新测量的面板控件的尺寸就能够了。咱们把表情区域放进一个自定义的布局控件KBPanelConflictLayout
,整个页面的根布局设为自定义控件KBRootConflictLayout
(代码能够看Demo中的解决切换键盘冲突)。
在KBRootConflictLayout
的onMeasure
方法中,根据布局高度变化是否超过某个阈值来判断是否键盘弹起。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
preNotifyChild(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void preNotifyChild(int width, int height) {
if (mOldHeight < 0) {
mOldHeight = height;
return ;
}
int deltaY = height - mOldHeight;
mOldHeight = height;
int minKeyboardHeight = 180;
if (Math.abs(deltaY) >= minKeyboardHeight) {
if (deltaY < 0) {
// 键盘弹起
if (mKBPanelConflictLayout != null) {
// 隐藏面板
mKBPanelConflictLayout.setHide();
}
} else {
// 键盘收起
if (mKBPanelConflictLayout != null) {
// 显示面板
mKBPanelConflictLayout.setShow();
}
}
}
}
复制代码
在KBPanelConflictLayout
的onMeasure
方法中,咱们根据是否隐藏状态来判断是否须要把键盘的高度变为0.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mHide) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setHide() {
this.mHide = true;
setVisibility(View.GONE);
}
public void setShow() {
this.mHide = false;
setVisibility(View.VISIBLE);
}
复制代码
这样在测试下,看看没有闪烁冲突的效果图吧。
若是你把上述代码整理优化下,加上对RelativeLayout和FrameLayout的支持,对设置是否调整面板高度与键盘一致的支持,对多面板的切换的支持,提供一些工具类给用户直接调用,那就是2000+star的github.com/Jacksgong/J…项目了。
附上本文Demo地址,欢迎点心: