安卓获取输入法高度与ViewTreeObserver讲解

  目录
  
  安卓获取输入法高度
  
  前言
  
  清单
  
  开始
  
  ViewTreeObserver讲解
  
  获取输入法高度原理
  
  思路
  
  实现
  
  关于ViewTreeObserver
  
  定义
  
  继承
  
  摘要
  
  获取View高度的三种方法
  
  源码
  
  interface KeyboardHeightObserver
  
  class KeyboardHeightProvider
  
  为了方便部分精力少的朋友, 本文开始就直接介绍安卓获取输入法高度的方法,然后再逐步讲解。
  
  安卓获取输入法高度
  
  前言
  
  在某些场景下, 比如写一个聊天界面,包括输入框和发送以及上面的消息列表,简单的使用LinearLayout或者RelativeLayout布局,当点击输入框,键盘弹起后,通常是不会遮挡输入框和发送的(有时就比较蛋疼了,不知为啥,它就是遮挡),因为它们也随键盘弹了起来。但布局再复杂点,比如说再加个表情栏或者更多栏,这样你肯定要手动控制输入框的高度了。因此,你就必须手动控制输入框的升降,但问题是升多高呢???这时,就要想办法获取输入法高度了(~ ̄▽ ̄)~
  
  由于目前安卓上还没有提供直接获取输入法高度的api,因此只好我们自己想办法获取它的高度了。
  
  注: 此思路由国外一大神提出,附上他的 Github ;
  
  清单
  
  这里有两个文件:
  
  interface KeyboardHeightObserver
  
  class KeyboardHeightProvider
  
  前一个用在待观测页面的作为回调函数, 后面是主要的方法所在的类了。
  
  开始
  
  文章后面会附上源码,引入这两个文件后,在要获取输入法高度的页面,首先实现接口KeyboardHeightObserver,即第一个文件,并重写里面的方法;
  
  然后再定义变量 KeyboardHeightProvider keyboardHeightProvider;
  
  实例化
  
  /**
  
  * Construct a new KeyboardHeightProvider
  
  *
  
  * @param activity The parent activity
  
  * @param layoutId   R.layout.*
  
  */
  
  // 以上为构造函数的相关注释,当然这里是我修改的,这样可以同时支持观测多个页面
  
  keyboardHeightProvider = new KeyboardHeightProvider(this, R.layout.activity_chat);
  
  new Handler().post(new Runnable() {
  
  @Override
  
  public void run() {
  
  keyboardHeightProvider.start();
  
  }
  
  });
  
  这时还要在onStart()函数里面加上 keyboardHeightProvider.setKeyboardHeightObserver(this); 即:
  
  @Override
  
  public void onStart() {
  
  super.onStart();
  
  // 这里使用了刚才实现的接口
  
  keyboardHeightProvider.setKeyboardHeightObserver(this);
  
  }
  
  考虑更全的话, 还可以加上以下语句:
  
  @Override
  
  public void onPause() {
  
  super.onPause();
  
  keyboardHeightProvider.setKeyboardHeightObserver(null);
  
  }
  
  @Override
  
  public void onDestroy() {
  
  super.onDestroy();
  
  keyboardHeightProvider.close();
  
  }
  
  这样一来,在回调函数 onKeyboardHeightChanged里面就回收到回调结果了,大功告成!
  
  ViewTreeObserver讲解
  
  这里就结合上面输入法的例子,讲讲ViewTreeObserver。
  
  获取输入法高度原理
  
  思路
  
  在要获取输入法高度的页面,创建一个看不见的弹窗,即宽为0,高为全屏,并为弹窗设置全局布局监听器。当布局有变化,比如有输入法弹窗出现或消失时, 监听器回调函数就会被调用。而其中的关键就是当输入法弹出时, 它会把之前我们创建的那个看不见的弹窗往上挤, 这样我们创建的那个弹窗的位置就变化了,只要获取它底部高度的变化值就可以间接的获取输入法的高度了。
  
  实现
  
  首先创建类KeyboardHeightProvider, 继承自PopupWindow;
  
  然后构造器内完成相关初始化:
  
  super(activity);
  
  this.activity = activity;
  
  LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
  
  this.popupView = inflator.inflate(layoutId, null, false);
  
  setContentView(popupView);
  
  setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
  
  setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  
  parentView = activity.findViewById(android.R.id.content);
  
  // 设置宽高
  
  setWidth(0);
  
  setHeight(WindowManager.LayoutParams.MATCH_PARENT);
  
  然后就是重点,为popupView的观测者(感觉用 ViewTreeObserver还是更合适)设置全局布局监听器
  
  popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  
  @Override
  
  public void onGlobalLayout() {
  
  if (popupView != null) {
  
  handleOnGlobalLayout();
  
  }
  
  }
  
  });
  
  其中handleOnGlobalLayout函数功能则是:获取弹窗高度,并作差得出输入法高度,以及通知回调。
  
  /**
  
  * Popup window itself is as big as the window of the Activity.
  
  * The keyboard can then be calculated by extracting the popup view bottom
  
  * from the activity window height.
  
  */
  
  private void handleOnGlobalLayout() {
  
  Point screenSize = new Point();
  
  activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
  
  Rect rect = new Rect();
  
  popupView.getWindowVisibleDisplayFrame(rect);
  
  // REMIND, you may like to change this using the fullscreen size of the phone
  
  // and also using the status bar and navigation bar heights of the phone to calculate
  
  // the keyboard height. But this worked fine on a Nexus.
  
  int orientation = getScreenOrientation();
  
  int keyboardHeight = screenSize.y - rect.bottom;
  
  if (keyboardHeight == 0) {
  
  notifyKeyboardHeightChanged(0, orientation);
  
  }
  
  else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
  
  this.keyboardPortraitHeight = keyboardHeight;
  
  notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
  
  }
  
  else {
  
  this.keyboardLandscapeHeight = keyboardHeight;
  
  notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
  
  }
  
  }
  
  嗯,大概就是这样(* ̄3 ̄)╭
  
  关于ViewTreeObserver
  
  定义
  
  首先自然要给出官方的定义:
  
  /**
  
  * A view tree observer is used to register listeners that can be notified of global
  
  * changes in the view tree. Such global events include, but are not limited to,
  
  * layout of the whole tree, beginning of the drawing pass, touch mode change....
  
  *
  
  * A ViewTreeObserver should never be instantiated by applications as it is provided
  
  * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()}
  
  * for more information.
  
  */
  
  翻译过来大概是
  
  // 原谅我英语不好(╯︿╰), 不过我发现谷歌翻译的效果还是不错的
  
  /**
  
  * 视图树观察器用于注册可以在视图树中通知全局
  
  * 更改的侦听器。此类全局事件包括但不限于
  
  * 整个树的布局,绘图过程的开始,触摸模式更改....
  
  *
  
  * ViewTreeObserver永远不应由应用程序实例化,因为它由视图层次结构提供
  
  * 。有关更多信息,请参阅{@link android.view.View#getViewTreeObserver()}
  
  * 。
  
  */
  
  继承
  
  java.lang.Object
  
  ↳    android.view.ViewTreeObserver
  
  直接继承自Object,没有另外的继承关系
  
  摘要
  
  Nested Classes
  
  interface    ViewTreeObserver.OnDrawListener    Interface definition for a callback to be invoked when the view tree is about to be drawn.
  
  interface    ViewTreeObserver.OnGlobalFocusChangeListener    Interface definition for a callback to be invoked when the focus state within the view tree changes.
  
  interface    ViewTreeObserver.OnGlobalLayoutListener    Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
  
  interface    ViewTreeObserver.OnPreDrawListener    Interface definition for a callback to be invoked when the view tree is about to be drawn.
  
  interface    ViewTreeObserver.OnScrollChangedListener    Interface definition for a callback to be invoked when something in the view tree has been scrolled.
  
  interface    ViewTreeObserver.www.haom178.com OnTouchModeChangeListener    Interface definition for a callback to be invoked when the touch mode changes.
  
  另外方法挺多的, 我就不列举了。
  
  获取View高度的三种方法
  
  注: 此处参考了小马快跑 的博客
  
  在某些时候,我们要获取view的高度,但获取到的为0,为什么呢?这样通常时由于页面还未测量导致的,比如在onCreate中调用的话就会直接返回0。这是就需要我们手动获取了。
  
  View的MeasureSpec.UNSPECIFIED
  
  通过设置View的MeasureSpec.UNSPECIFIED来测量:
  
  int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
  
  int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
  
  view.measure(w, h);
  
  //获得宽高
  
  int viewWidth=view.getMeasuredWidth();
  
  int viewHeight=view.getMeasuredHeight();
  
  设置我们的SpecMode为UNSPECIFIED,然后去调用onMeasure测量宽高,就可以得到宽高。
  
  ViewTreeObserver .addOnGlobalLayoutListener
  
  通过ViewTreeObserver www.quwanyule157.com.addOnGlobalLayoutListener来获得宽高,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行:
  
  //获得ViewTreeObserver
  
  ViewTreeObserver observer=view.getViewTreeObserver();
  
  //注册观察者,监听变化
  
  observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  
  @Override
  
  public void onGlobalLayout(www.michenggw.com/) {
  
  //判断ViewTreeObserver 是否alive,如果存活的话移除这个观察者
  
  if(observer.isAlive()){
  
  observer.removeGlobalOnLayoutListener(this);
  
  //获得宽高
  
  int viewWidth=view.getMeasuredWidth();
  
  int viewHeight=view.getMeasuredHeight();
  
  }
  
  }
  
  });
  
  ViewTreeObserver .addOnPreDrawListener
  
  通过ViewTreeObserver .addOnPreDrawListener来获得宽高,在执行onDraw之前已经执行了onLayout()和onMeasure(),可以得到宽高了,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行
  
  //获得ViewTreeObserver
  
  ViewTreeObserver observer=view.getViewTreeObserver();
  
  //注册观察者,监听变化
  
  observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
  
  @Override
  
  public boolean onPreDraw(www.dasheng178.com/  ) {
  
  if(observer.isAlive()www.furggw.com   ){
  
  observer.removeOnDrawListener(this);
  
  }
  
  //获得宽高
  
  int viewWidth=view.getMeasuredWidth();
  
  int viewHeight=view.getMeasuredHeight();
  
  return true;
  
  }
  
  });
  
  源码
  
  interface KeyboardHeightObserver
  
  public interface KeyboardHeightObserver {
  
  /**
  
  * Called when the keyboard height has changed, 0 means keyboard is closed,
  
  * >= 1 means keyboard is opened.
  
  *
  
  * @param height        The height of the keyboard in pixels
  
  * @param orientation   The orientation either: Configuration.ORIENTATION_PORTRAIT or
  
  *                      Configuration.ORIENTATION_LANDSCAPE
  
  */
  
  void onKeyboardHeightChanged(int height, int orientation);
  
  }
  
  class KeyboardHeightProvider
  
  import android.app.Activity;
  
  import android.content.res.Configuration;
  
  import android.graphics.Point;
  
  import android.graphics.Rect;
  
  import android.graphics.drawable.ColorDrawable;
  
  import android.view.Gravity;
  
  import android.view.LayoutInflater;
  
  import android.view.View;
  
  import android.view.ViewTreeObserver;
  
  import android.view.WindowManager;
  
  import android.widget.PopupWindow;
  
  /**
  
  * The keyboard height provider, this class uses a PopupWindow
  
  * to calculate the window height when the floating keyboard is opened and closed.
  
  */
  
  public class KeyboardHeightProvider extends PopupWindow {
  
  /** The tag for logging purposes */
  
  private final static String TAG = "sample_KeyboardHeightProvider";
  
  /** The keyboard height observer */
  
  private KeyboardHeightObserver observer;
  
  /** The cached landscape height of the keyboard */
  
  private int keyboardLandscapeHeight;
  
  /** The cached portrait height of the keyboard */
  
  private int keyboardPortraitHeight;
  
  /** The view that is used to calculate the keyboard height */
  
  private View popupView;
  
  /** The parent view */
  
  private View parentView;
  
  /** The root activity that uses this KeyboardHeightProvider */
  
  private Activity activity;
  
  /**
  
  * Construct a new KeyboardHeightProvider
  
  *
  
  * @param activity The parent activity
  
  * @param layoutId   R.layout.*
  
  */
  
  public KeyboardHeightProvider(Activity activity, int layoutId) {
  
  super(activity);
  
  this.activity = activity;
  
  LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
  
  this.popupView = inflator.inflate(layoutId, null, false);
  
  setContentView(popupView);
  
  setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
  
  setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
  
  parentView = activity.findViewById(android.R.id.content);
  
  setWidth(0);
  
  setHeight(WindowManager.LayoutParams.MATCH_PARENT);
  
  popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  
  @Override
  
  public void onGlobalLayout() {
  
  if (popupView != null) {
  
  handleOnGlobalLayout();
  
  }
  
  }
  
  });
  
  }
  
  /**
  
  * Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
  
  * PopupWindows are not allowed to be registered before the onResume has finished
  
  * of the Activity.
  
  */
  
  public void start() {
  
  if (!isShowing() && parentView.getWindowToken() != null) {
  
  setBackgroundDrawable(new ColorDrawable(0));
  
  showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
  
  }
  
  }
  
  /**
  
  * Close the keyboard height provider,
  
  * this provider will not be used anymore.
  
  */
  
  public void close() {
  
  this.observer = null;
  
  dismiss();
  
  }
  
  /**
  
  * Set the keyboard height observer to this provider. The
  
  * observer will be notified when the keyboard height has changed.
  
  * For example when the keyboard is opened or closed.
  
  *
  
  * @param observer The observer to be added to this provider.
  
  */
  
  public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
  
  this.observer = observer;
  
  }
  
  /**
  
  * Get the screen orientation
  
  *
  
  * @return the screen orientation
  
  */
  
  private int getScreenOrientation() {
  
  return activity.getResources().getConfiguration().orientation;
  
  }
  
  /**
  
  * Popup window itself is as big as the window of the Activity.
  
  * The keyboard can then be calculated by extracting the popup view bottom
  
  * from the activity window height.
  
  */
  
  private void handleOnGlobalLayout() {
  
  Point screenSize = new Point();
  
  activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
  
  Rect rect = new Rect();
  
  popupView.getWindowVisibleDisplayFrame(rect);
  
  // REMIND, you may like to change this using the fullscreen size of the phone
  
  // and also using the status bar and navigation bar heights of the phone to calculate
  
  // the keyboard height. But this worked fine on a Nexus.
  
  int orientation = getScreenOrientation();
  
  int keyboardHeight = screenSize.y - rect.bottom;
  
  if (keyboardHeight == 0) {
  
  notifyKeyboardHeightChanged(0, orientation);
  
  }
  
  else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
  
  this.keyboardPortraitHeight = keyboardHeight;
  
  notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
  
  }
  
  else {
  
  this.keyboardLandscapeHeight = keyboardHeight;
  
  notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
  
  }
  
  }
  
  private void notifyKeyboardHeightChanged(int height, int orientation) {
  
  if (observer != null) {
  
  observer.onKeyboardHeightChanged(height, orientation);

猜你喜欢

转载自blog.csdn.net/li123128/article/details/84962552