为了方便部分精力少的朋友, 本文开始就直接介绍安卓获取输入法高度的方法,然后再逐步讲解。

安卓获取输入法高度

前言

  1. 在某些场景下, 比如写一个聊天界面,包括输入框和发送以及上面的消息列表,简单的使用LinearLayout或者RelativeLayout布局,当点击输入框,键盘弹起后,通常是不会遮挡输入框和发送的(有时就比较蛋疼了,不知为啥,它就是遮挡),因为它们也随键盘弹了起来。但布局再复杂点,比如说再加个表情栏或者更多栏,这样你肯定要手动控制输入框的高度了。因此,你就必须手动控制输入框的升降,但问题是升多高呢???这时,就要想办法获取输入法高度了(~ ̄▽ ̄)~

由于目前安卓上还没有提供直接获取输入法高度的api,因此只好我们自己想办法获取它的高度了。

注: 此思路由国外一大神提出,附上他的 Github ;

清单

这里有两个文件:

  • interface KeyboardHeightObserver
  • class KeyboardHeightProvider

前一个用在待观测页面的作为回调函数, 后面是主要的方法所在的类了。

开始

文章后面会附上源码,引入这两个文件后,在要获取输入法高度的页面,首先实现接口KeyboardHeightObserver,即第一个文件,并重写里面的方法;

然后再定义变量 KeyboardHeightProvider keyboardHeightProvider;

实例化

  1. /**
  2. * Construct a new KeyboardHeightProvider
  3. *
  4. * @param activity The parent activity
  5. * @param layoutId R.layout.*
  6. */
  7. // 以上为构造函数的相关注释,当然这里是我修改的,这样可以同时支持观测多个页面
  8. keyboardHeightProvider = new KeyboardHeightProvider(this, R.layout.activity_chat);
  9. new Handler().post(new Runnable() {
  10. @Override
  11. public void run() {
  12. keyboardHeightProvider.start();
  13. }
  14. });

这时还要在onStart()函数里面加上 keyboardHeightProvider.setKeyboardHeightObserver(this); 即:

  1. @Override
  2. public void onStart() {
  3. super.onStart();
  4. // 这里使用了刚才实现的接口
  5. keyboardHeightProvider.setKeyboardHeightObserver(this);
  6. }

考虑更全的话, 还可以加上以下语句:

  1. @Override
  2. public void onPause() {
  3. super.onPause();
  4. keyboardHeightProvider.setKeyboardHeightObserver(null);
  5. }
  6. @Override
  7. public void onDestroy() {
  8. super.onDestroy();
  9. keyboardHeightProvider.close();
  10. }

这样一来,在回调函数 onKeyboardHeightChanged里面就回收到回调结果了,大功告成!

ViewTreeObserver讲解

这里就结合上面输入法的例子,讲讲ViewTreeObserver。

获取输入法高度原理

思路

在要获取输入法高度的页面,创建一个看不见的弹窗,即宽为0,高为全屏,并为弹窗设置全局布局监听器。当布局有变化,比如有输入法弹窗出现或消失时, 监听器回调函数就会被调用。而其中的关键就是当输入法弹出时, 它会把之前我们创建的那个看不见的弹窗往上挤, 这样我们创建的那个弹窗的位置就变化了,只要获取它底部高度的变化值就可以间接的获取输入法的高度了。

实现

首先创建类KeyboardHeightProvider, 继承自PopupWindow;

然后构造器内完成相关初始化:


  1. super(activity);
  2. this.activity = activity;
  3. LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
  4. this.popupView = inflator.inflate(layoutId, null, false);
  5. setContentView(popupView);
  6. setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
  7. setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  8. parentView = activity.findViewById(android.R.id.content);
  9. // 设置宽高
  10. setWidth(0);
  11. setHeight(WindowManager.LayoutParams.MATCH_PARENT);

然后就是重点,为popupView的观测者(感觉用 ViewTreeObserver还是更合适)设置全局布局监听器

  1. popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  2. @Override
  3. public void onGlobalLayout() {
  4. if (popupView != null) {
  5. handleOnGlobalLayout();
  6. }
  7. }
  8. });

其中handleOnGlobalLayout函数功能则是:获取弹窗高度,并作差得出输入法高度,以及通知回调。

  1. /**
  2. * Popup window itself is as big as the window of the Activity.
  3. * The keyboard can then be calculated by extracting the popup view bottom
  4. * from the activity window height.
  5. */
  6. private void handleOnGlobalLayout() {
  7. Point screenSize = new Point();
  8. activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
  9. Rect rect = new Rect();
  10. popupView.getWindowVisibleDisplayFrame(rect);
  11. // REMIND, you may like to change this using the fullscreen size of the phone
  12. // and also using the status bar and navigation bar heights of the phone to calculate
  13. // the keyboard height. But this worked fine on a Nexus.
  14. int orientation = getScreenOrientation();
  15. int keyboardHeight = screenSize.y - rect.bottom;
  16. if (keyboardHeight == 0) {
  17. notifyKeyboardHeightChanged(0, orientation);
  18. }
  19. else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
  20. this.keyboardPortraitHeight = keyboardHeight;
  21. notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
  22. }
  23. else {
  24. this.keyboardLandscapeHeight = keyboardHeight;
  25. notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
  26. }
  27. }

嗯,大概就是这样(* ̄3 ̄)╭

关于ViewTreeObserver

定义

首先自然要给出官方的定义:

  1. /**
  2. * A view tree observer is used to register listeners that can be notified of global
  3. * changes in the view tree. Such global events include, but are not limited to,
  4. * layout of the whole tree, beginning of the drawing pass, touch mode change....
  5. *
  6. * A ViewTreeObserver should never be instantiated by applications as it is provided
  7. * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
  8. * for more information.
  9. */

翻译过来大概是

  1. // 原谅我英语不好(╯︿╰), 不过我发现谷歌翻译的效果还是不错的
  2. /**
  3. * 视图树观察器用于注册可以在视图树中通知全局
  4. * 更改的侦听器。此类全局事件包括但不限于
  5. * 整个树的布局,绘图过程的开始,触摸模式更改....
  6. *
  7. * ViewTreeObserver永远不应由应用程序实例化,因为它由视图层次结构提供
  8. * 。有关更多信息,请参阅{@link android.view.View#getViewTreeObserver()}
  9. * 。
  10. */

继承

  1. java.lang.Object
  2. android.view.ViewTreeObserver

直接继承自Object,没有另外的继承关系

摘要

Nested Classes
interface
interface
interface
interface
interface
interface

另外方法挺多的, 我就不列举了。

获取View高度的三种方法

注: 此处参考了小马快跑 的博客

在某些时候,我们要获取view的高度,但获取到的为0,为什么呢?这样通常时由于页面还未测量导致的,比如在onCreate中调用的话就会直接返回0。这是就需要我们手动获取了。

View的MeasureSpec.UNSPECIFIED

通过设置View的MeasureSpec.UNSPECIFIED来测量:

  1. int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
  2. int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
  3. view.measure(w, h);
  4. //获得宽高
  5. int viewWidth=view.getMeasuredWidth();
  6. int viewHeight=view.getMeasuredHeight();

设置我们的SpecMode为UNSPECIFIED,然后去调用onMeasure测量宽高,就可以得到宽高。

ViewTreeObserver .addOnGlobalLayoutListener

通过ViewTreeObserver .addOnGlobalLayoutListener来获得宽高,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行:

  1. //获得ViewTreeObserver
  2. ViewTreeObserver observer=view.getViewTreeObserver();
  3. //注册观察者,监听变化
  4. observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  5. @Override
  6. public void onGlobalLayout() {
  7. //判断ViewTreeObserver 是否alive,如果存活的话移除这个观察者
  8. if(observer.isAlive()){
  9. observer.removeGlobalOnLayoutListener(this);
  10. //获得宽高
  11. int viewWidth=view.getMeasuredWidth();
  12. int viewHeight=view.getMeasuredHeight();
  13. }
  14. }
  15. });

ViewTreeObserver .addOnPreDrawListener

通过ViewTreeObserver .addOnPreDrawListener来获得宽高,在执行onDraw之前已经执行了onLayout()和onMeasure(),可以得到宽高了,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行

  1. //获得ViewTreeObserver
  2. ViewTreeObserver observer=view.getViewTreeObserver();
  3. //注册观察者,监听变化
  4. observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
  5. @Override
  6. public boolean onPreDraw() {
  7. if(observer.isAlive()){
  8. observer.removeOnDrawListener(this);
  9. }
  10. //获得宽高
  11. int viewWidth=view.getMeasuredWidth();
  12. int viewHeight=view.getMeasuredHeight();
  13. return true;
  14. }
  15. });

源码

interface KeyboardHeightObserver


  1. public interface KeyboardHeightObserver {
  2. /**
  3. * Called when the keyboard height has changed, 0 means keyboard is closed,
  4. * >= 1 means keyboard is opened.
  5. *
  6. * @param height The height of the keyboard in pixels
  7. * @param orientation The orientation either: Configuration.ORIENTATION_PORTRAIT or
  8. * Configuration.ORIENTATION_LANDSCAPE
  9. */
  10. void onKeyboardHeightChanged(int height, int orientation);
  11. }

class KeyboardHeightProvider


  1. import android.app.Activity;
  2. import android.content.res.Configuration;
  3. import android.graphics.Point;
  4. import android.graphics.Rect;
  5. import android.graphics.drawable.ColorDrawable;
  6. import android.view.Gravity;
  7. import android.view.LayoutInflater;
  8. import android.view.View;
  9. import android.view.ViewTreeObserver;
  10. import android.view.WindowManager;
  11. import android.widget.PopupWindow;
  12. /**
  13. * The keyboard height provider, this class uses a PopupWindow
  14. * to calculate the window height when the floating keyboard is opened and closed.
  15. */
  16. public class KeyboardHeightProvider extends PopupWindow {
  17. /** The tag for logging purposes */
  18. private final static String TAG = "sample_KeyboardHeightProvider";
  19. /** The keyboard height observer */
  20. private KeyboardHeightObserver observer;
  21. /** The cached landscape height of the keyboard */
  22. private int keyboardLandscapeHeight;
  23. /** The cached portrait height of the keyboard */
  24. private int keyboardPortraitHeight;
  25. /** The view that is used to calculate the keyboard height */
  26. private View popupView;
  27. /** The parent view */
  28. private View parentView;
  29. /** The root activity that uses this KeyboardHeightProvider */
  30. private Activity activity;
  31. /**
  32. * Construct a new KeyboardHeightProvider
  33. *
  34. * @param activity The parent activity
  35. * @param layoutId R.layout.*
  36. */
  37. public KeyboardHeightProvider(Activity activity, int layoutId) {
  38. super(activity);
  39. this.activity = activity;
  40. LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
  41. this.popupView = inflator.inflate(layoutId, null, false);
  42. setContentView(popupView);
  43. setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
  44. setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  45. parentView = activity.findViewById(android.R.id.content);
  46. setWidth(0);
  47. setHeight(WindowManager.LayoutParams.MATCH_PARENT);
  48. popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  49. @Override
  50. public void onGlobalLayout() {
  51. if (popupView != null) {
  52. handleOnGlobalLayout();
  53. }
  54. }
  55. });
  56. }
  57. /**
  58. * Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
  59. * PopupWindows are not allowed to be registered before the onResume has finished
  60. * of the Activity.
  61. */
  62. public void start() {
  63. if (!isShowing() && parentView.getWindowToken() != null) {
  64. setBackgroundDrawable(new ColorDrawable(0));
  65. showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
  66. }
  67. }
  68. /**
  69. * Close the keyboard height provider,
  70. * this provider will not be used anymore.
  71. */
  72. public void close() {
  73. this.observer = null;
  74. dismiss();
  75. }
  76. /**
  77. * Set the keyboard height observer to this provider. The
  78. * observer will be notified when the keyboard height has changed.
  79. * For example when the keyboard is opened or closed.
  80. *
  81. * @param observer The observer to be added to this provider.
  82. */
  83. public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
  84. this.observer = observer;
  85. }
  86. /**
  87. * Get the screen orientation
  88. *
  89. * @return the screen orientation
  90. */
  91. private int getScreenOrientation() {
  92. return activity.getResources().getConfiguration().orientation;
  93. }
  94. /**
  95. * Popup window itself is as big as the window of the Activity.
  96. * The keyboard can then be calculated by extracting the popup view bottom
  97. * from the activity window height.
  98. */
  99. private void handleOnGlobalLayout() {
  100. Point screenSize = new Point();
  101. activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
  102. Rect rect = new Rect();
  103. popupView.getWindowVisibleDisplayFrame(rect);
  104. // REMIND, you may like to change this using the fullscreen size of the phone
  105. // and also using the status bar and navigation bar heights of the phone to calculate
  106. // the keyboard height. But this worked fine on a Nexus.
  107. int orientation = getScreenOrientation();
  108. int keyboardHeight = screenSize.y - rect.bottom;
  109. if (keyboardHeight == 0) {
  110. notifyKeyboardHeightChanged(0, orientation);
  111. }
  112. else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
  113. this.keyboardPortraitHeight = keyboardHeight;
  114. notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
  115. }
  116. else {
  117. this.keyboardLandscapeHeight = keyboardHeight;
  118. notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
  119. }
  120. }
  121. private void notifyKeyboardHeightChanged(int height, int orientation) {
  122. if (observer != null) {
  123. observer.onKeyboardHeightChanged(height, orientation);
  124. }
  125. }
  126. }

安卓获取输入法高度与ViewTreeObserver讲解的更多相关文章

  1. Android开发 - 获取系统输入法高度的正确姿势

    问题与解决 在Android应用的开发中,有一些需求需要我们获取到输入法的高度,但是官方的API并没有提供类似的方法,所以我们需要自己来实现. 查阅了网上很多资料,试过以后都不理想. 比如有的方法通过 ...

  2. js获取浏览器高度

    常用: JS 获取浏览器窗口大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 获取窗口宽度 if (window.innerWidth) winWidth = ...

  3. js 获取浏览器高度和宽度值(多浏览器)(转)

    IE中: document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> BODY对象高度 document.d ...

  4. JavaScript获取浏览器高度和宽度值(documentElement,clientHeight,offsetHeight,scrollHeight,scrollTop,offsetParent,offsetY,innerHeight)

    IE中: document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> BODY对象高度 document.d ...

  5. 原生js获取鼠标坐标方法全面讲解-zmq

    原生js获取鼠标坐标方法全面讲解:clientX/Y,pageX/Y,offsetX/Y,layerX/Y,screenX/Y 一.关于js鼠标事件综合各大浏览器能获取到坐标的属性总共以下五种:eve ...

  6. $(window).height()获取浏览器高度不准

    以前在开发的时候这样$(window).height()获取浏览器的高度一致不觉得有什么不对, 今天在做java开发的时候不知道为什么获取的高度很明显不对. 后来无意中想到一个文档模式不对的原因,于是 ...

  7. JavaScript获取浏览器高度和宽度值

    IE中:  document.body.clientWidth ==> *DY对象宽度 document.body.clientHeight ==> *DY对象高度 document.do ...

  8. 转:JS获取浏览器高度和宽度

    发现一篇好文章,汇总到自己的网站上. IE中: document.body.clientWidth ==> BODY对象宽度 document.body.clientHeight ==> ...

  9. JS获取浏览器高度 并赋值给类

    在给网站做轮播焦点图的时候,如果需要全屏的话,可以用下面的jQuery来获取浏览器高度,然后赋值给类. $(window).load(function () { var maxHeight = 0; ...

随机推荐

  1. delphi声明类及其调用方法

    {type放在interface下的uses引用单元下面} 1 // 声明类 type TMyclass = class //注意这里不能加';' 因为这是个整体 data: integer; //类 ...

  2. 15分XX秒后订单自动关闭(倒计时)

    //订单记录 function get_order(){ //请求订单ajax方法 XX.send_api("method",{data},function(){ var date ...

  3. 较为复杂的 GraphQL 查询实现

    一.实现功能首页各类排行榜加载数据: 向下遍历子节点并展开: 1.展开过程中动态加载简介summary.书类bookType: 2.book对象上包裹Rank节点,描述book对象在不同排行榜下所处位 ...

  4. T-SQL查询进阶--理解SQL Server中索引的概念,原理

    简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,sql server仍然可以实现应有的功能,但索引可以在大多数情况下提升查询性能,在OLAP(On line Trans ...

  5. angular 管道

    import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'multi' }) export class MultiPipe ...

  6. 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架 - LinFx

    LinFx 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架,遵循领域驱动设计(DDD)规范约束,提供实现事件驱动.事件回溯.响应式等特性的基础设施.让开发者享受到正真意义的面向对象 ...

  7. ubuntu14.04,安装Chrome(谷歌浏览器)

    Linux:ubuntu14.04 一直都很喜欢谷歌浏览器,进入linux怎么能没有? 安装方法:谷歌浏览器官方下载的ubuntu版本,下载后点击即可安装. 下载地址:http://download. ...

  8. [转] Draw Call未被批处理?告诉你在Unity 5.6中如何查找原因 [复制链接]

    Unity在5.6之前的版本中并未提供很直接的方式来查找Draw Call未被批处理的原因,但Unity 5.6在Frame Debugger中新增了一项功能,帮助开发者查找相关信息.今天这篇文章就为 ...

  9. 从图片中提取html格式的布局

    制作界面的自动化 意义:对于程序设计人员来说,比较痛苦的是制作界面.从设计人员发送的稿件(通常为jpg格式),到完成html的布局,需要将图像转化为div标记,自动化这一过程十分有意义 1.设计一个工 ...

  10. THU 上机 最小邮票数 暴力枚举

    链接:https://www.nowcoder.com/questionTerminal/83800ae3292b4256b7349ded5f178dd1?toCommentId=2533792来源: ...