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

安卓获取输入法高度

前言

在某些场景下, 比如写一个聊天界面,包括输入框和发送以及上面的消息列表,简单的使用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
interface
interface
interface
interface
interface

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

获取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 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);
}
}
}

安卓获取输入法高度与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. WebApi跨域问题解决

    因为第一次用webapi,并且还是前后台分离,所以设置到了跨域,在百度上找了很多解决办法,但是基本都存在缺陷,我这里分享一下我自己的经验 1.首先配置Web.config 这样配置发布到服务器就可以跨 ...

  2. OC字符串与C语言字符串之间的相互转换

    1.C转OC字符串 const char *cString = "This is a C string"; // 动态方法 NSString *ocString1 = [[NSSt ...

  3. 特殊用途语言特性(默认实参/内联函数/constexpr函数/assert预处理宏/NDEBUG预处理变量)

    默认实参: 某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参.调用含有默认实参的函数时,可以包含该实参,也可以省略该实参. 需要特别 ...

  4. hexo的jacman主题配置

    获得更多资料欢迎进入我的网站或者 csdn或者博客园 这是在我搭建博客时用的主题,这个主题时基于pacman修改的,同时我也是借助于wuchong同时他还在一直更新.一下时我的一些基本配置: 相关文章 ...

  5. Qt 学习之路 2(69):进程

    Qt 学习之路 2(69):进程 豆子 2013年11月9日 Qt 学习之路 2 15条评论 进程是操作系统的基础之一.一个进程可以认为是一个正在执行的程序.我们可以把进程当做计算机运行时的一个基础单 ...

  6. 使用spring,pageHelper ,注解完成分页。

    第一步 maven 依赖pageHeler aspectj 开启aop的注解 基础工作完成! 第二步: 建立一个注解 建立注解实现,建立环绕通知 最后一步:加上 @EnablePaging 注解 就可 ...

  7. GCD(最大公约数)和LCM(最小公倍数)的求法

    GCD(最大公约数) (1)辗转相除法(欧几里得算法)(常用) 将两个数a, b相除,如果余数c不等于0,就把b的值给a,c的值给b,直到c等于0,此时最大公约数就是b (2)更相减损术 将两个书中较 ...

  8. python-is,==

    在讲is和==这两种运算符区别之前,首先要知道Python中对象包含的三个基本要素,分别是:id(身份标识).python type()(数据类型)和value(值).is和==都是对对象进行比较判断 ...

  9. HDU6312 Game(博弈,拿出本数与这个数的除数)

    题意:A和B玩游戏 , 给出1 ~ n 的集合 ,每个人可以拿出一个数 , 这个数的除数也被拿出 , A先开始 , 没有数拿的人就输 , 问A赢不赢 分析:很有意思的一道题目 ///假设2 ~ n A ...

  10. openLayers地图缩放的回调

    //设置地图最小缩放级别为17级 map.events.register("zoomend", this, function (e) { //每次地图缩放时就会进入到这 if (m ...