背景

  1. Android软键盘的显示和隐藏,从开始做直播这块,就一直困扰着我。
  2. 从布局挤压,到输入区显示不全,再到闪屏以及卡顿,这里的坑让我跌倒无数次。
  3. 各种布局监听,回调,代码冗余、复杂、与业务强耦合无法复用,还是没有很好的解决键盘的弹出和隐藏
  4. 为了给用户更好的操作体验,决定找到一种最优解决方案。

科普基础知识-WindowSoftInputMode

Activity 的主窗口与包含屏幕软键盘的窗口的交互方式。改属性的设置影响两个方面:

  • 当Activity成为用户注意的焦点时软键盘的状态-隐藏还是可见
  • 对Activity主窗口所做的调整-是否将其尺寸调小以为软键盘腾出空间,或者当窗口部分被软键盘遮挡时是否平移其那内容使当前焦点可见。

该设置必须是下标所列的值之一,或者一个state...值加上一个adjust...值的组合。在任一一组设置多个值(例如,多个state...值)都会产生未定义结果。各个值之间使用垂直条(|)分割。

说明
stateUnspecified 不指定软键盘的状态(隐藏还是可见)。 将由系统选择合适的状态,或依赖主题中的设置。这是对软键盘行为的默认设置。
stateUnchanged 当 Activity 转至前台时保留软键盘最后所处的任何状态,无论是可见还是隐藏。
stateHidden 当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 隐藏软键盘。
stateAlwaysHidden 当 Activity 的主窗口有输入焦点时始终隐藏软键盘。
stateVisible 在正常的适宜情况下(当用户向前导航到 Activity的主窗口时)显示软键盘。
stateAlwaysVisible 当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 显示软键盘。
adjustUnspecified 不指定 Activity 的主窗口是否调整尺寸以为软键盘腾出空间,或者窗口内容是否进行平移以在屏幕上显露当前焦点。 系统会根据窗口的内容是否存在任何可滚动其内容的布局视图来自动选择其中一种模式。 如果存在这样的视图,窗口将进行尺寸调整,前提是可通过滚动在较小区域内看到窗口的所有内容。这是对主窗口行为的默认设置。
adjustResize 始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间
adjustPan 不调整 Activity 主窗口的尺寸来为软键盘腾出空间, 而是自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容。 这通常不如尺寸调正可取,因为用户可能需要关闭软键盘以到达被遮盖的窗口部分或与这些部分进行交互。

官方解释

网上解决方案

第一种方案

由于Activity.onKeyDownn()是监听不到向下的按键,所以自定义Edittext,重写onKeyPreIme方法

/*** 拦截键盘向下按键的 EditTextView*/
public class TextEditTextView extends DmtEditText {public TextEditTextView(Context context) {super(context);}public TextEditTextView(Context context, AttributeSet attrs) {super(context, attrs);}public TextEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onKeyPreIme(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == 1 && onKeyBoardHideListener != null) {onKeyBoardHideListener.onKeyHide();}return super.onKeyPreIme(keyCode, event);}/*** 键盘监听接口*/private OnKeyBoardHideListener onKeyBoardHideListener;public void setOnKeyBoardHideListener(OnKeyBoardHideListener onKeyBoardHideListener) {this.onKeyBoardHideListener = onKeyBoardHideListener;}public interface OnKeyBoardHideListener {void onKeyHide();}
}

为什么重写onKeyDown()方法,监听不到虚拟键的向下按键,而重写EditTextView的onKeyPreIme可以监听到,这篇博客写的很明白。

第二种方案

使用ViewTreeObserver.OnGlobalLayoutListener来监听整个布局的变化,但是有问题,点击软键盘的“向下”按键,不会回调这个函数。

View.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){//当键盘弹出隐藏的时候会 调用此方法。@Overridepublic void onGlobalLayout() {final Rect rect = new Rect();activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);final int screenHeight = activity.getWindow().getDecorView().getRootView().getHeight();final int heightDifference = screenHeight - rect.bottom;boolean visible = heightDifference > screenHeight / 3;if(visible){Log.i(TAG,"软键盘显示");}else {Log.i(TAG,"软键盘隐藏");}}
});

第三种方案

将布局撑满全屏,来监听onMeasure()的变化,这种方式可以生效,但是和业务耦合性太大,而且在全面屏的时候处理比较麻烦,就不贴代码了

总结

基本上所有监听软键盘的方式,都是通过上面的三种方式实现的。实现效果比较好是Android键盘面板冲突 布局闪动处理方案

所有的方式都是在本页面上去弹起软键盘,软键盘是dialog,而且页面的逻辑和复杂程度有各种情况,非常难以考虑。真是让人抓耳挠腮呀。

打个响指,换种思路

没有非要在一个页面内完成软键盘的调度,我们可以另起炉灶,去实现这个功能。

使用DialogFragment来实现软键盘功能

这种情况还是与业务有一定的关联,我不敢说这种方案能够解决所有情况下的软键盘使用问题,但是能解决很多情况下的使用。

先说一下我的使用场景,因为我是负责直播模块的开发,软键盘的弹出和隐藏时的输入部分ui是不相同。使用抖音的直播举个例子

可以看到软键盘在打开和关闭的时候是不同的ui,那么就可以使用DialogFragment来实现功能。我们最主要实现就是监听软键盘的弹出和隐藏,弹出问题不大,这里最深的坑就是监听键盘的消失,先总结一下键盘消失的场景:

  1. 点击空白区域消失
  2. 点击虚拟键能向下按键隐藏键盘
  3. 点击软键盘向下按钮隐藏键盘
  4. 点击发送按钮后,消失键盘

ps: 2和3,看上去好像是一样的啊,大家肯定有一些疑惑,看图说话

从张图中可以清晰的看出来两者的区别,其实android的原生键盘是没有向下的按钮的,各个第三方的输入法自己实现的,andorid中没有回调可以监听到这个事件(坑爹啊)。而且
ViewTreeObserver 监听不到点击这个按钮时的布局变化(坑爹啊!!!),我只是在mix2手机上测试的,其他的手机类型我不敢确保也是同样的问题。

来来来,在做个小结

  • 软键盘的弹出时可以监听的
  • 消失几种情况中,除了软键盘的向下按键其他都可以做到监听,或者可以拿到触发的时机。

搜了一大圈,好像没有找到比较好的解决方案,怎么办呢?但是别人家的直播都是可以做到的呀,这时看到了View.getWindowVisibleDisplayFrame()方法,来看下官方的解释

/*** Retrieve the overall visible display size in which the window this view is* attached to has been positioned in.  This takes into account screen* decorations above the window, for both cases where the window itself* is being position inside of them or the window is being placed under* then and covered insets are used for the window to position its content* inside.  In effect, this tells you the available area where content can* be placed and remain visible to users.** <p>This function requires an IPC back to the window manager to retrieve* the requested information, so should not be used in performance critical* code like drawing.** @param outRect Filled in with the visible display frame.  If the view* is not attached to a window, this is simply the raw display size.*/public void getWindowVisibleDisplayFrame(Rect outRect) {if (mAttachInfo != null) {try {mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);} catch (RemoteException e) {return;}// XXX This is really broken, and probably all needs to be done// in the window manager, and we need to know more about whether// we want the area behind or in front of the IME.final Rect insets = mAttachInfo.mVisibleInsets;outRect.left += insets.left;outRect.top += insets.top;outRect.right -= insets.right;outRect.bottom -= insets.bottom;return;}// The view is not attached to a display so we don't have a context.// Make a best guess about the display size.Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);d.getRectSize(outRect);}

大概意思:这个api是用来获取窗口可视区域大小的。该大小会收到系统状态栏、软键盘和虚拟按键的影响。在应用开发中可以利用该api来获取状态栏的高度,软键盘的高度和虚拟按键的高度。

解决方案:
既然没有回调能够拿到虚拟键盘的向下操作,那么我们就轮询监听窗口的大小,由于这个dialogFragment的生命周期只是在输入的时候存在,那么就以为这这个轮询时间也不会太长,100ms轮询一次来监听窗口变化的大小,完美解决(如果还有其他优雅的解决方案,请告诉我)。

用DialogFragment实现输入区域的好处:

  1. 输入部分的逻辑与其他业务本分的逻辑隔离,实现解耦
  2. DialogFragment也是一个dialog,可以单独处理键盘弹出时的逻辑

有了以上两点考虑,我就开始动手写代码

Version 1

源代码就不贴了,太长了,而且大部分和业务相关,所有的业务逻辑和功能逻辑全部写在DialogFragment中,看上去没什么问题,如果突然有一天,另外的一个地方要做到类似的逻辑,又要重新写一遍功能逻辑,完全不能复用呀,这样的实现方案是不行的,打回去重做。嗯,需要将业务逻辑和功能逻辑分割开,这样可以很大程度上的复用当前代码。

Version 2

首先要定义一个接口,来定义此类功能的统一调用方式,所有要实现此功能的类都要实现这个接口

public interface IKeyBoard {// EditTextView需要继承TextEditTextView,TextEditTextView是用来监听虚拟按键向下的操作TextEditTextView getEditTextView();// 根布局View getRoot();// dialogFragment 销毁时调用,也就是隐藏时void onDismiss();
}

自定义EditTextView

/*** 拦截键盘向下按键的 EditTextView*/
public class TextEditTextView extends DmtEditText {public TextEditTextView(Context context) {super(context);}public TextEditTextView(Context context, AttributeSet attrs) {super(context, attrs);}public TextEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onKeyPreIme(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == 1 && onKeyBoardHideListener != null) {onKeyBoardHideListener.onKeyHide();}return super.onKeyPreIme(keyCode, event);}/*** 键盘监听接口*/private OnKeyBoardHideListener onKeyBoardHideListener;public void setOnKeyBoardHideListener(OnKeyBoardHideListener onKeyBoardHideListener) {this.onKeyBoardHideListener = onKeyBoardHideListener;}public interface OnKeyBoardHideListener {void onKeyHide();}
}

最主要的类来了:

/*** 这个类时用来实现输入框随软键盘弹出的情况,这个dialogFragment只负责弹起键盘的操作,不负责具体的ui显示和逻辑,* 应该实现一个UI类来实现相应的显示和逻辑部分。* <p>* 使用方法:* 1. 需要实现{@link IKeyBoard}* 2. UI类需要包含{@link TextEditTextView}* 3. 如果UI需要监听声明周期,需要实现{@link LifecycleObserver}* <p>* ps:父类的Fragment或者Activity  window的setSoftInputMode设置为{@link WindowManager} SOFT_INPUT_ADJUST_NOTHING** @author liyachao* @date 2018/4/17*/public class KeyBoardDialogFragment extends DialogFragment implements TextEditTextView.OnKeyBoardHideListener,WeakHandler.IHandler {private static final String TAG = "KeyBoardDialogFragment";private IKeyBoard mKeyBoardView;private TextEditTextView mTextEditTextView;private boolean softKeyBoardIsVisible;private Activity mActivity;private WeakHandler mHandler;private Rect mRect = new Rect();public static KeyBoardDialogFragment newInstance(IKeyBoard keyBoard) {KeyBoardDialogFragment fragment = new KeyBoardDialogFragment();Bundle args = new Bundle();fragment.setArguments(args);fragment.setKeyBoardView(keyBoard);return fragment;}/*** 安全检查* @param keyBoardView 业务逻辑的view*/public void setKeyBoardView(IKeyBoard keyBoardView) {if (keyBoardView == null) {throw new RuntimeException("keyBoardView must not be null");} else if (keyBoardView.getEditTextView() == null) {throw new RuntimeException("keyBoardView must has EditTextView");} else if (keyBoardView.getRoot() == null) {throw new RuntimeException("keyBoardView must has root layout");}mKeyBoardView = keyBoardView;mTextEditTextView = keyBoardView.getEditTextView();}/*** 设置主题 input_dialog_style_large的具体设置如下* <style name="input_dialog_style_large" parent="@android:style/Theme.Dialog">*         <item name="android:windowBackground">@color/transparent</item> //winndow 背景为透明色 *         <item name="android:windowNoTitle">true</item> // 没有title*         <item name="android:backgroundDimEnabled">false</item> // 没有默认的背景色*         <item name="android:windowAnimationStyle">@style/keyboard_dialog_animation</item> //window动画,可以不设置*     </style>* 业务逻辑view,注册DialogFragment声明周期* */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setStyle(STYLE_NO_TITLE, R.style.input_dialog_style_large);if (mKeyBoardView == null || mKeyBoardView.getRoot() == null) {dismiss();return;}if (mKeyBoardView.getRoot() instanceof LifecycleObserver) {getLifecycle().addObserver((LifecycleObserver) mKeyBoardView.getRoot());}mHandler = new WeakHandler(this);}@Overridepublic void onAttach(Context activity) {super.onAttach(activity);mActivity = (Activity) activity;}@Nullable@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return mKeyBoardView.getRoot();}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);mTextEditTextView.setOnKeyBoardHideListener(this);initWindowParams();}/*** 设置window属性*/public void initWindowParams() {Window window = getDialog().getWindow();if (window == null) {return;}WindowManager.LayoutParams lp = getDialog().getWindow().getAttributes();lp.dimAmount = 0;lp.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;lp.width = ViewGroup.LayoutParams.MATCH_PARENT;lp.gravity = Gravity.BOTTOM;window.setBackgroundDrawable(new ColorDrawable(0));window.setAttributes(lp);window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);}@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {Dialog dialog = super.onCreateDialog(savedInstanceState);dialog.setCanceledOnTouchOutside(true);return dialog;}@Overridepublic void onResume() {super.onResume();mHandler.sendEmptyMessageDelayed(1, 100);}@Overridepublic void onStop() {super.onStop();dismissAllowingStateLoss();mHandler.removeMessages(1);}@Overridepublic void onDestroy() {super.onDestroy();}@Overridepublic void onDismiss(DialogInterface dialog) {super.onDismiss(dialog);mKeyBoardView.onDismiss();}@Overridepublic void onKeyHide() {dismiss();}public void onGlobalLayout() {Window window = getDialog().getWindow();if (window != null) {mRect.setEmpty();window.getDecorView().getWindowVisibleDisplayFrame(mRect);int screenHeight = UIUtils.getScreenHeight(getContext());int heightDifference = screenHeight - (mRect.bottom - mRect.top);if (heightDifference > screenHeight / 3) {Log.d(TAG, "键盘弹出");softKeyBoardIsVisible = true;} else {if (softKeyBoardIsVisible) {Log.d(TAG, "键盘隐藏");dismiss();softKeyBoardIsVisible = false;}}}}@Overridepublic void handleMsg(Message msg) {if (msg.what == 1) {onGlobalLayout();mHandler.sendEmptyMessageDelayed(1, 100);}}
}

具体的实现就是上面了,大部分做了注释,也不需要解释了,基本上可以解决大部分场景,可以根据自己具体的业务逻辑做一些改动。demo就不给大家了,上面基本上就可以了。

demo下载

优雅的监听软键盘隐藏相关推荐

  1. android 软键盘 状态,Android监听软键盘状态

    监听软键盘隐藏或显示,代码如下: /** * 监听软键盘状态 */ private void listenerInput() { final LinearLayout ll_main = (Linea ...

  2. android 键盘隐藏监听,安卓监听软键盘弹出与隐藏的两种方法

    需求: 现在有一个需求是点击一行文本框,弹出一个之前隐藏的输入框,输入完成后按返回键或者其他的东西隐藏键盘和输入框,将输入框的内容填充到文本框中. 实现: 拿到这个需求的第一反应就是写一个监听来监听键 ...

  3. Android App监听软键盘按键的三种方式(转)

    最近有类似需求,在csdn上刚好发现,粘贴过来,以防止忘记喽 前言: 我们在android手机上面有时候会遇到监听手机软键盘按键的时候,例如:我们在浏览器输入url完毕后可以点击软键盘右下角的&quo ...

  4. Android App监听软键盘按键的三种方式

    前言: 我们在android手机上面有时候会遇到监听手机软键盘按键的时候,例如:我们在浏览器输入url完毕后可以点击软键盘右下角的"GO"按键加载url页面:在点击搜索框的时候,点 ...

  5. android 代码设置 键盘适应_Android自适应软键盘的Dialog以及监听软键盘弹起

    最近项目中遇到一个需求:新手引导.跟一般的新手引导没有什么太大区别,思路都是搞一个带阴影的遮罩层,然后在上边儿给一些提示性的文字,由于需求中有些特殊的地方,所以我用了一个全屏的dialog(而且,di ...

  6. 键盘-App监听软键盘按键的三种方式

    前言: 我们在android手机上面有时候会遇到监听手机软键盘按键的时候,例如:我们在浏览器输入url完毕后可以点击软键盘右下角的"GO"按键加载url页面:在点击搜索框的时候,点 ...

  7. h5 移动端 监听软键盘弹起、收起

    前面一篇博客 h5 安卓 键盘弹起界面适配 修改webview高度提到了在adnroid中如何监听软键盘的弹起与收起,是利用的窗口的高度发生变化 window.onresize事件来做突破点的,但是i ...

  8. 【Android应用】【监听软键盘弹起与关闭】

    [背景] 在很多App开发过程中需要在Activity中监听Android设备的软键盘弹起与关闭,但是Android似乎没有提供相关的的监听API给我们来调用,本文提供了一个可行的办法来监听软键盘的弹 ...

  9. Android 监听软键盘的高度并解决其覆盖输入框的问题

    1.前言 在某些项目中,我们常常需要自定义一个输入框,软键盘弹出时就把输入框顶上去,关闭时输入框再回到原位(比如下方的效果图,实际上各种 App 中的聊天界面和发布评论的界面大体都是这样).在这个过程 ...

  10. Android App监听软键盘按键的三种方式与改变软键盘右下角确定键样式

    Android App监听软键盘按键的三种方式与改变软键盘右下角确定键样式 actionNone : 回车键,按下后光标到下一行 actionGo : Go, actionSearch : 放大镜 a ...

最新文章

  1. 不同系统之间数据的交互
  2. 【C 语言】文件操作 ( 文件加密解密 | 使用第三方 DES 加密解密库 | 头文件导入 | 兼容 C++ 语言 | 加密解密函数说明 )
  3. 降维(二)----Laplacian Eigenmaps
  4. iOS 分类思想(2)
  5. 如何使用安装光盘为本机创建yum repository
  6. SAP Spartacus的发布方式以及语义化版本管理机制
  7. UITabBarController使用总结
  8. rufus中gpt和mrb磁盘_计算机关于磁盘的大杂烩
  9. iOS简单动画实现方案
  10. 寒假打工去,不能继续写博客,表示好心痛啊。。。
  11. c语言中短路逻辑有与有或例子,C语言零基础教程之运算符和表达式,全面解析,轻松上手...
  12. 本地mongo 连接远程数据库_本地搭建esaymock
  13. Silicio for Mac(迷你播放器)免费版
  14. MVC模式中编写一个登录的Servlet
  15. RePast J介绍
  16. 机器学习(周志华)读书笔记 1
  17. 基本初等函数导数公式表
  18. 计算机科学与技术如何创新,计算机科学与技术专业创新能力的培养途径
  19. 生活随记 - 国庆假期怎么过才有意义呢
  20. 3-4-搭建自己的vue-ssr

热门文章

  1. python实现微信自动发信息软件_Python实现给微信好友自动发送消息的示例
  2. 白盒测试哪种测试效果好_白盒测试与黑盒测试区别(简答题)简短一些不要长的谢谢...
  3. 电子电工产品成品及材料灼热丝测试用试验仪
  4. mac地址查 计算机名字,怎么看mac地址-教你通过MAC地址查询设备的厂商名称
  5. 解决Excel 闪退问题(如果你最近装了visio的话点进来)
  6. 相关滤波之开篇Mosse原理及代码详解
  7. RN对接京东支付sdk(Android)
  8. MongoDB(芒果数据库)学习(一)———增删改查
  9. 大数据采集与处理期末复习题
  10. 高响应比优先调度算法和短作业优先调度算法