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

愿君学长松,慎勿作桃李。这篇文章主要讲述安卓获取输入法高度与ViewTreeObserver讲解相关的知识,希望能为你提供帮助。
目录

  • 安卓获取输入法高度
    • 前言
    • 清单
    • 开始
  • ViewTreeObserver讲解
    • 获取输入法高度原理
      • 思路
      • 实现
    • 关于ViewTreeObserver
      • 定义
      • 继承
      • 摘要
      • 获取View高度的三种方法
  • 源码
    • interface KeyboardHeightObserver
    • class KeyboardHeightProvider
为了方便部分精力少的朋友, 本文开始就直接介绍安卓获取输入法高度的方法,然后再逐步讲解。
安卓获取输入法高度 前言
在某些场景下, 比如写一个聊天界面,包括输入框和发送以及上面的消息列表,简单的使用LinearLayout或者RelativeLayout布局,当点击输入框,键盘弹起后,通常是不会遮挡输入框和发送的(有时就比较蛋疼了,不知为啥,它就是遮挡),因为它们也随键盘弹了起来。但布局再复杂点,比如说再加个表情栏或者更多栏,这样你肯定要手动控制输入框的高度了。因此,你就必须手动控制输入框的升降,但问题是升多高呢???这时,就要想办法获取输入法高度了(~ ̄▽ ̄)~

由于目前安卓上还没有提供直接获取输入法高度的api,因此只好我们自己想办法获取它的高度了。
注: 此思路由国外一大神提出,附上他的 Github ;
清单【安卓获取输入法高度与ViewTreeObserver讲解】这里有两个文件:
  • interface KeyboardHeightObserver
  • class KeyboardHeightProvider
前一个用在待观测页面的作为回调函数, 后面是主要的方法所在的类了。
开始文章后面会附上源码,引入这两个文件后,在要获取输入法高度的页面,首先实现接口KeyboardHeightObserver,即第一个文件,并重写里面的方法;
然后再定义变量 KeyboardHeightProvider keyboardHeightProvider;
实例化
/** * Construct a new KeyboardHeightProvider * * @param activity The parent activity * @param layoutIdR.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.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 .addOnGlobalLayoutListener来获得宽高,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行:
//获得ViewTreeObserver ViewTreeObserver observer=view.getViewTreeObserver(); //注册观察者,监听变化 observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //判断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() { if(observer.isAlive()){ 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 heightThe height of the keyboard in pixels * @param orientationThe 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 layoutIdR.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); } } }


    推荐阅读