一般我们为了让键盘自动将界面弹起,会在清单文件中配置windowSoftInputMode,配置为adjustResize或adjustPan。

adjustResize,键盘弹起时,将界面Layout高度压缩,留出空间显示软键盘。

adjustPan,需要存在滚动控件,键盘弹起时,滚动列表,留出控件显示软键盘,如果没有滚动控件,则将全部控件上移,而不会压缩界面Layout。

package="com.hule.dashi.live">

android:name=".AudioLiveRoomActivity"

android:windowSoftInputMode="adjustResize" />

遇到的问题

在直播模块开发中,需要将状态栏透明。然而透明后,却发现一个大坑,软键盘模式失效了。无论windowSoftInputMode设置adjustResize还是adjustPan,键盘都会将界面覆盖,而不会将输入框顶起。

搜索了一番,结果发现是谷歌在2.2年代就留下的坑,却一直没修复...那么我们是不是可以监听软键盘弹起事件,获取软键盘高度,手动压缩布局Layout高度不就可以了?

结果Android并没有提供软键盘弹起的监听事件,擦,这么坑的?那么还有什么办法么?

办法还是有的

搜索了一下,原来已经有大神提供了处理类,名叫AndroidBug5497Workaround,大概原理就是给Activity的根View注册ViewTreeObserver.OnGlobalLayoutListener。键盘弹起时,监听会回调,我们拿取原Activity始布局高度和Activity可见高度进行相减,即可计算出键盘高度,再将布局高度重新设置,就可以将布局上移。同时可以将键盘改变作为回调监听提供给外部。

结果又发现问题,在刘海屏上有问题,底部会空出一大片,而我修改后的做法是:计算出键盘高度后,给布局底部控件设置一个MarginBottom,自然会有一种顶出输入框的效果。

源码解析

首先是生成软键盘的监听。提供给外部监听软键盘监听。

首先我们传入rootView构造KeyboardFix。

给rootView设置addOnGlobalLayoutListener回调。

addOnGlobalLayoutListener的onGlobalLayout回调时,就是键盘弹起和下降。

possiblyResizeChildOfContent(),computeUsableHeight()计算可见高度。

可见高度小于原始高度一定阀值,则当做为键盘弹起。否则为下降软键盘。

最后回调监听者。

public class KeyboardFix implements ViewTreeObserver.OnGlobalLayoutListener {

/**

* 父容器

*/

private View vRootView;

/**

* 父容器的高度

*/

private int mContentHeight;

/**

* 标记,第一次回调时保存父容器的高度

*/

private boolean isFirst = true;

/**

* 最后一次显示父容器的高度,用于判断是否显示虚拟键盘

*/

private int mLastShowHeight;

//1.首先我们传入rootView构造KeyboardFix。

public KeyboardFix(View rootView) {

if (rootView != null) {

//2. 给rootView设置addOnGlobalLayoutListener回调。

rootView.getViewTreeObserver().addOnGlobalLayoutListener(this);

}

}

@Override

public void onGlobalLayout() {

//3. addOnGlobalLayoutListener的onGlobalLayout回调时,就是键盘弹起和下降。

possiblyResizeChildOfContent();

}

/**

* 重新调整跟布局的高度

*/

private void possiblyResizeChildOfContent() {

//4. possiblyResizeChildOfContent(),computeUsableHeight()计算可见高度。

//计算内容的可见高度

int usableHeightNow = computeUsableHeight();

if (isFirst) {

//兼容华为等机型

mContentHeight = usableHeightNow;

isFirst = false;

}

//没有改变,忽略

if (usableHeightNow == mLastShowHeight) {

return;

}

mLastShowHeight = usableHeightNow;

//5. 可见高度小于原始高度一定阀值,则当做为键盘弹起。否则为下降软键盘。

boolean isShowKeyboard = usableHeightNow + 200 < mContentHeight;

//6. 最后回调监听者。

for (OnKeyboardChangeCallback listener : mOnKeyboardChangeCallbacks) {

listener.onChange(isShowKeyboard, mContentHeight, usableHeightNow);

}

}

/**

* 计算内容的可见高度

*

* @return 父容器的可见高度

*/

private int computeUsableHeight() {

Rect rect = new Rect();

vRootView.getWindowVisibleDisplayFrame(rect);

return (rect.bottom - rect.top);

}

/**

* 虚拟键盘显示隐藏的监听

*/

public interface OnKeyboardChangeCallback {

/**

* 虚拟键盘显示发生变化时调用

*

* @param isVisible true为可见,false为不可见

* @param contentHeight 原始内容高度

* @param usableHeight 当前可用高度

*/

void onChange(boolean isVisible, int contentHeight, int usableHeight);

/**

* 界面暂时回调

*/

void onPause();

/**

* 界面销毁时回调

*/

void onDestroy();

}

}

拓展KeyboardFix,处理输入框弹起

平时我们使用软键盘模式,最多就是将输入框上移,而透明状态栏让键盘模式失效,那么让输入框上移的活,就只能我们自己做了。

原理:

例如我们给输入框设置一个父容器,点击输入框弹起软键盘,这时我们的键盘回调会回调,计算软键盘高度,我们将软键盘高度作为输入框父容器的MarginBottom,就形成了输入框被软键盘弹起的情况。

由于这种情况很通用,所以可以抽取为一个通用的Callback。同时为Callback提供生命周期回调。

/**

* 回调空实现

*/

public static class OnKeyboardChangeAdapter implements OnKeyboardChangeCallback {

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

}

@Override

public void onPause() {

}

@Override

public void onDestroy() {

}

}

/**

* 存在输入框场景的监听,键盘弹起时,设置输入框距离底部一个键盘高度,键盘下降时取消距离

*/

public static class CallbackToInput extends OnKeyboardChangeAdapter {

/**

* 输入框容器

*/

private View vInputContainer;

public CallbackToInput(View inputContainer) {

vInputContainer = inputContainer;

}

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

super.onChange(isVisible, contentHeight, usableHeight);

if (isVisible) {

showInputView(usableHeight, contentHeight);

} else {

setBottomMargin(0);

}

}

@Override

public void onPause() {

super.onPause();

//界面暂停时,下降输入框

setBottomMargin(0);

}

/**

* 显示虚拟键盘时调用,显示输入框,如果绑定了列表控件则滚动到后一个item

*

* @param height 用于计算虚拟键盘的高度

*/

private void showInputView(int height, int contentHeight) {

if (vInputContainer == null) {

return;

}

//虚拟键盘的高度

int keyboardHeight = contentHeight - height;

setBottomMargin(keyboardHeight);

}

/**

* 重新设置inputContainer的底部边距

*/

private void setBottomMargin(int bottomMargin) {

if (vInputContainer == null) {

return;

}

ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) vInputContainer.getLayoutParams();

if (params.bottomMargin != bottomMargin) {

params.bottomMargin = bottomMargin;

vInputContainer.requestLayout();

}

}

}

使用监听

mKeyboardFix.addOnKeyboardChangeListener(new KeyboardFix.CallbackToInput(inputContainer));

拓展KeyboardFix,键盘弹起,自动滚动RecyclerView

像QQ、微信,软键盘弹起时,滚动列表控件到底部,由于我们已经有了软键盘回调了,所以处理起来就很简单了,一样我们将这种通用的行为封装为Callback即可。

/**

* 存在列表场景的监听,一般键盘弹起时,列表需要滚动到底部,就可以添加该类型监听

*/

public static class CallbackToList extends OnKeyboardChangeAdapter {

private RecyclerView vRecyclerView;

private boolean mIsReverse;

/**

* 是否反转,反转则滚动到第0位,非反转则滚动到列表的最后1位

*

* @param isReverse true为反转

*/

public CallbackToList(RecyclerView recyclerView, boolean isReverse) {

vRecyclerView = recyclerView;

mIsReverse = isReverse;

}

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

super.onChange(isVisible, contentHeight, usableHeight);

//键盘弹起时滚动到底部

if (isVisible) {

int position;

if (mIsReverse) {

position = 0;

} else {

if (vRecyclerView.getAdapter() == null) {

return;

}

position = vRecyclerView.getAdapter().getItemCount() - 1;

}

if (vRecyclerView != null && vRecyclerView.getAdapter() != null) {

vRecyclerView.scrollToPosition(position);

}

}

}

}

使用监听

mKeyboardFix.addOnKeyboardChangeListener(new KeyboardFix.CallbackToList(recyclerView, false));

分发生命周期事件

最后要记得在Activity或Fragment分发生命周期事件给KeyboardFix。

@Override

protected void onPause() {

super.onPause;

if (mKeyboardFix != null) {

mKeyboardFix.onPause();

}

}

@Override

protected void onDestroy() {

super.onDestroy();

if (mKeyboardFix != null) {

mKeyboardFix.onDestroy();

}

}

完整代码

public class KeyboardFix implements ViewTreeObserver.OnGlobalLayoutListener {

/**

* 父容器

*/

private View vRootView;

/**

* 父容器的高度

*/

private int mContentHeight;

/**

* 标记,第一次回调时保存父容器的高度

*/

private boolean isFirst = true;

/**

* 最后一次显示父容器的高度,用于判断是否显示虚拟键盘

*/

private int mLastShowHeight;

/**

* 键盘监听

*/

private List mOnKeyboardChangeCallbacks;

/**

* 根容器

*/

public KeyboardFix(View rootView) {

mOnKeyboardChangeCallbacks = new CopyOnWriteArrayList<>();

vRootView = rootView;

if (rootView != null) {

rootView.getViewTreeObserver().addOnGlobalLayoutListener(this);

}

}

@Override

public void onGlobalLayout() {

possiblyResizeChildOfContent();

}

public void addOnKeyboardChangeListener(OnKeyboardChangeCallback onKeyboardChangeCallback) {

mOnKeyboardChangeCallbacks.add(onKeyboardChangeCallback);

}

/**

* 重新调整跟布局的高度

*/

private void possiblyResizeChildOfContent() {

//计算内容的可见高度

int usableHeightNow = computeUsableHeight();

if (isFirst) {

//兼容华为等机型

mContentHeight = usableHeightNow;

isFirst = false;

}

//没有改变,忽略

if (usableHeightNow == mLastShowHeight) {

return;

}

mLastShowHeight = usableHeightNow;

//判断是否弹起软键盘

boolean isShowKeyboard = usableHeightNow + 200 < mContentHeight;

for (OnKeyboardChangeCallback listener : mOnKeyboardChangeCallbacks) {

listener.onChange(isShowKeyboard, mContentHeight, usableHeightNow);

}

}

/**

* 计算内容的可见高度

*

* @return 父容器的可见高度

*/

private int computeUsableHeight() {

Rect rect = new Rect();

vRootView.getWindowVisibleDisplayFrame(rect);

return (rect.bottom - rect.top);

}

/**

* 界面暂时时调用

*/

public void onPause() {

if (mOnKeyboardChangeCallbacks != null) {

for (OnKeyboardChangeCallback callback : mOnKeyboardChangeCallbacks) {

callback.onPause();

}

}

}

/**

* 界面销毁时调用,取消注册,防止内存泄漏

*/

public void onDestroy() {

if (vRootView != null) {

vRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

vRootView = null;

}

if (mOnKeyboardChangeCallbacks != null) {

for (OnKeyboardChangeCallback callback : mOnKeyboardChangeCallbacks) {

callback.onDestroy();

}

mOnKeyboardChangeCallbacks.clear();

mOnKeyboardChangeCallbacks = null;

}

}

/**

* 虚拟键盘显示隐藏的监听

*/

public interface OnKeyboardChangeCallback {

/**

* 虚拟键盘显示发生变化时调用

*

* @param isVisible true为可见,false为不可见

* @param contentHeight 原始内容高度

* @param usableHeight 当前可用高度

*/

void onChange(boolean isVisible, int contentHeight, int usableHeight);

/**

* 界面暂时回调

*/

void onPause();

/**

* 界面销毁时回调

*/

void onDestroy();

}

/**

* 回调空实现

*/

public static class OnKeyboardChangeAdapter implements OnKeyboardChangeCallback {

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

}

@Override

public void onPause() {

}

@Override

public void onDestroy() {

}

}

/**

* 显示软键盘

*/

public void showSoftInput(final View view) {

InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

if (manager == null) {

return;

}

view.setFocusable(true);

view.setFocusableInTouchMode(true);

view.requestFocus();

manager.showSoftInput(view, InputMethodManager.SHOW_FORCED);

}

/**

* 存在输入框场景的监听,键盘弹起时,设置输入框距离底部一个键盘高度,键盘下降时取消距离

*/

public static class CallbackToInput extends OnKeyboardChangeAdapter {

/**

* 输入框容器

*/

private View vInputContainer;

public CallbackToInput(View inputContainer) {

vInputContainer = inputContainer;

}

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

super.onChange(isVisible, contentHeight, usableHeight);

if (isVisible) {

showInputView(usableHeight, contentHeight);

} else {

setBottomMargin(0);

}

}

@Override

public void onPause() {

super.onPause();

//界面暂停时,下降输入框

setBottomMargin(0);

}

/**

* 显示虚拟键盘时调用,显示输入框,如果绑定了列表控件则滚动到后一个item

*

* @param height 用于计算虚拟键盘的高度

*/

private void showInputView(int height, int contentHeight) {

if (vInputContainer == null) {

return;

}

//虚拟键盘的高度

int keyboardHeight = contentHeight - height;

setBottomMargin(keyboardHeight);

}

/**

* 重新设置inputContainer的底部边距

*/

private void setBottomMargin(int bottomMargin) {

if (vInputContainer == null) {

return;

}

ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) vInputContainer.getLayoutParams();

if (params.bottomMargin != bottomMargin) {

params.bottomMargin = bottomMargin;

vInputContainer.requestLayout();

}

}

}

/**

* 存在列表场景的监听,一般键盘弹起时,列表需要滚动到底部,就可以添加该类型监听

*/

public static class CallbackToList extends OnKeyboardChangeAdapter {

private RecyclerView vRecyclerView;

private boolean mIsReverse;

/**

* 是否反转,反转则滚动到第0位,非反转则滚动到列表的最后1位

*

* @param isReverse true为反转

*/

public CallbackToList(RecyclerView recyclerView, boolean isReverse) {

vRecyclerView = recyclerView;

mIsReverse = isReverse;

}

@Override

public void onChange(boolean isVisible, int contentHeight, int usableHeight) {

super.onChange(isVisible, contentHeight, usableHeight);

//键盘弹起时滚动到底部

if (isVisible) {

int position;

if (mIsReverse) {

position = 0;

} else {

if (vRecyclerView.getAdapter() == null) {

return;

}

position = vRecyclerView.getAdapter().getItemCount() - 1;

}

if (vRecyclerView != null && vRecyclerView.getAdapter() != null) {

vRecyclerView.scrollToPosition(position);

}

}

}

}

}

总结

虽然谷歌没有提供键盘监听,但是我们可以曲线救国,当然希望官方可以修复这个bug,并且提供回调为最好,本篇作为记录而写。

android软键盘和导航栏冲突,Android透明状态栏和软键盘配合的坑相关推荐

  1. android软键盘和导航栏冲突,Android隐藏导航栏/保持沉浸式模式与软键盘外观

    我想出了一个解决方法,检查每个内部的导航栏的状态,尝试隐藏它,并再次检查(再次). 这是一些代码,确保在软键盘关闭后的2秒内导航栏被隐藏. private final Runnable checkSy ...

  2. android仿咸鱼底部导航栏,Flutter沉浸式状态栏/AppBar导航栏/仿咸鱼底部凸起导航栏效果...

    如下图:状态栏是指android手机顶部显示手机状态信息的位置. android 自4.4开始新加入透明状态栏功能,状态栏可以自定义颜色背景,使titlebar能够和状态栏融为一体,增加沉浸感. 如上 ...

  3. android仿微信的activity平滑水平切换动画,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    Android实现简单底部导航栏 Android仿微信滑动切换效果 发布时间:2020-10-09 19:48:00 来源:脚本之家 阅读:96 作者:丶白泽 Android仿微信滑动切换最终实现效果 ...

  4. 史上最完美的Android沉浸式状态导航栏攻略

    前言 最近我在小破站开发一款新App,叫高能链.我是一个完美主义者,所以不管对架构还是UI,我都是比较抠细节的,在状态栏和导航栏沉浸式这一块,我还是踩了挺多坑,费了挺多精力的.这次我将我踩坑,适配各机 ...

  5. android 导航栏位置,android手机导航栏

    /** * 设置透明状态栏与导航栏 * @param navi true不设置导航栏|false设置导航栏 */ public void setStatusBar(boolean navi) { // ...

  6. Android UI体验之全屏沉浸式透明状态栏效果

    前言: Android 4.4之后谷歌提供了沉浸式全屏体验, 在沉浸式全屏模式下, 状态栏. 虚拟按键动态隐藏, 应用可以使用完整的屏幕空间, 按照 Google 的说法, 给用户一种 身临其境 的体 ...

  7. JS实现导航栏下滑悬浮透明置顶

    如果仅仅想把导航栏固定,添加以下属性即可: style="position: sticky;" 注:前提是你已经写好了导航栏. 如果想将导航栏下滑悬浮透明,请参照如下方式: 给 h ...

  8. Android 检查设备是否存在 导航栏 NavigationBar

    http://blog.csdn.NET/lnb333666/article/details/41821149 目前也没有可靠的方法来检查设备上是否有导航栏.可以使用KeyCharacterMap.d ...

  9. android 打造炫酷导航栏(仿UC头条)

    年后开始上班甚是清闲,所以想捣鼓一些东西.在翻阅大神杰作Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI 的时候看到下面有一条评论说,如果导航栏能滑动就更好了. ...

  10. android功能导航布局,Android全面屏虚拟导航栏适配

    手机正朝着全面屏的方向演进,与此同时也给开发者带来了很多适配上的新问题,虚拟导航栏就是其中一个.最近在糗百的项目中,就有相关的适配问题,我查阅了目前关于虚拟导航栏适配的相关文章,基本上在全面屏手机里都 ...

最新文章

  1. 三种 MySQL 大表优化方案
  2. office 2007 验证失败的解决方法
  3. raid0+磁盘加密
  4. Android笔记之FragmentTabHost实现选项卡
  5. php 执行命令屏幕输出捕捉,在php执行linux命令时显示所有输出
  6. 一句话讲清楚Python的垃圾回收有啥用
  7. python中减法运算函数_详解 Python 的二元算术运算,为什么说减法只是语法糖?...
  8. 8月读书分享-《执行力是训练出来的》
  9. html标签始终在右下角,html+javascript实现图片始终在页面右下角
  10. Java实现ActiveMQ之队列的生产者和消费者(一)
  11. 洛谷 P1352 没有上司的舞会【树形DP/邻接链表+链式前向星】
  12. Django视图层:Django便捷函数,render()函数返回HttpResponse对象,redirect()函数返回HttpResponseRedirect指向传递参数的URL
  13. 谈谈我的跳槽感想,从日资企业到互联网的转变
  14. 百家号在线视频编辑器的技术演进
  15. Java网络编程(精简版)
  16. 常用的三种非对称加密算法
  17. astc贴图格式是什么意思_Unity 分离贴图 alpha 通道实践
  18. python可以excel_python能处理excel吗
  19. 安装应用宝统一链接服务器,数据互通|安卓应用宝部分区服服务器数据互通维护公告...
  20. yuque-hexo:语雀写文,自动部署 Hexo 博客

热门文章

  1. QFile读取移动硬盘文件卡死问题
  2. character not supported here
  3. 坐拥深圳7栋房,月收租60万!房东却选择开出租……
  4. Python制作个性二维码
  5. 微信开放平台-移动应用
  6. 关于Fabric中shim包的问题
  7. 计算机有哪些专业技能,简历计算机技能有哪些
  8. 【视频检测】Flow-Guided Feature Aggregation for Video Object Detection
  9. Android项目实践(二)——日记本APP
  10. Locality-Aware NMS 局部感知NMS(LNMS)学习