目录
安卓获取输入法高度
前言
清单
开始
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 Windows itself is as big as the Windows of the Activity.
- * The keyboard can then be calculated by extracting the popup view bottom
- * from the activity Windows 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 高度的三种方法
注: 此处参考了小马快跑 的博客 https://www.jianshu.com/p/5b026ffc36f1
在某些时候, 我们要获取 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 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 Windows 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 Windows itself is as big as the Windows of the Activity.
- * The keyboard can then be calculated by extracting the popup view bottom
- * from the activity Windows 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);
- }
- }
- }
来源: https://www.cnblogs.com/asche/p/10104459.html