Android 获取 View 宽高的常用正确方式,避免为零

摘要:
所以,当我们在Activity的生命周期方法中直接获取View的宽高时,View也许还没执行完measure阶段,那么自然获取到的宽高结果为0。借此机制,巧妙获取View的宽高属性。但是局限性在于,在Activity中使用代码创建View的场景较少,一般都是获取layout文件所加载View的宽高。只有当View至少经历过一次layout时,isLaidOut()方法才能返回true,继而才能获取到View的真实宽高。

相信有很多朋友都有过在 Activity 中通过 getWidth() 之类的方法获取 View 的宽高值,可能在 onCreate() 生命周期方法中,也可能在 onResume() 生命周期方法中。然而,不幸的是,并不能获取所要的结果,宽高值均为 0。

如果对 View 的绘制显示流程熟悉的话,就会知道问题所在。我们知道,在自定义 View 时,通常都要重写 onMeasure、onLayout、onDraw 这几个方法。同理,Activity 的内容显示到设备上时,这些 View 也要经历这些阶段。

所以,当我们在 Activity 的生命周期方法中直接获取 View 的宽高时,View 也许还没执行完 measure 阶段,那么自然获取到的宽高结果为 0。这也提醒我们一点,在 onCreate 方法中只适合做些一些初始化设置工作,使用 View 执行动画或者其他操作时,一定要注意考虑 View 绘制的耗时过程。

那么问题来了,怎么样才能在 Activity 代码中获取到 View 的实际宽高值呢?这里给大家总结一些常用方法。

addOnGlobalLayoutListener
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        if (Build.VERSION.SDK_INT >= 16) {
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }else {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
        int width = view.getWidth();
    }
});

ViewTreeObserver,顾名思义,视图树的观察者,可以监听 View 的全局变化事件。比如,layout 变化,draw 事件等。可以阅读源码了解更多事件。

注意:使用时需要注意及时移除该事件的监听,避免后续每一次发生全局 View 变化均触发该事件,影响性能。这里用的是 OnGlobalLayoutListener,移除监听时注意 API 的版本兼容处理。

addOnPreDrawListener
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        view.getViewTreeObserver().removeOnPreDrawListener(this);
        int width = view.getWidth();
        return false;
    }
});

这里同样是利用 ViewTreeObserver 观察者类,只不过这里监听的是 draw 事件。

view.post()
view.post(new Runnable() {
    @Override
    public void run() {
        int width = view.getWidth();
    }
});

利用 Handler 通信机制,添加一个 Runnable 到 message queue 中,当 view layout 处理完成时,自动发送消息,通知 UI 线程。借此机制,巧妙获取 View 的宽高属性。代码简洁,使用简单,相比 ViewTreeObserver 监听处理,还不需要手动移除观察者监听事件。

onLayout()
view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int = view.getWidth(); 
    }
};

利用 View 绘制过程中的 onLayout() 方法获取宽高值,这也是为一个不需要借助其他类或者方法,仅靠自己就能完成获取宽高属性的手段。但是局限性在于,在 Activity 中使用代码创建 View 的场景较少,一般都是获取 layout 文件所加载 View 的宽高。

addOnLayoutChangeListener
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        view.removeOnLayoutChangeListener(this);
        int width = view.getWidth();
    }
});

监听 View 的 onLayout() 绘制过程,一旦 layout 触发变化,立即回调 onLayoutChange 方法。注意,用完也要注意调用 remove 方法移除监听事件。

ViewCompat.isLaidOut(view)
if (ViewCompat.isLaidOut(view)) {
    int width = view.getWidth();
}

严格意义上来讲,这不能作为一个获取宽高的方式之一。充其量只能是一个判断条件。只有当 View 至少经历过一次 layout 时,isLaidOut() 方法才能返回 true,继而才能获取到 View 的真实宽高。所以,当我们的代码中有多次调用获取宽高时,才有可能使用这个方法判断处理。

getMeasuredWidth()

最后,借此地儿补充一点知识,getMeasuredWidth() 与 getWidth() 或者 getMeasuredHeight() 与 getHeight() 的区别。很多人容易对此产生混淆,不知道这两个方法到底有什么区别,使用时应该如何取舍。其实,官方文档介绍 View class 时,对于 Size 部分,有这么一段话:

The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.

The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().

The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().

这段话足以解释 getMeasuredXXX() 与 getXXX() 的区别和联系所在。说得直白一点,measuredWidth 与 width 分别对应于视图绘制 的 measure 与 layout 阶段。很重要的一点是,我们要明白,View 的宽高是由 View 本身和 parent 容器共同决定的,要知道有这个 MeasureSpec 类的存在。

比如,View 通过自身 measure() 方法向 parent 请求 100x100 的宽高,那么这个宽高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 阶段,通过 childview.layout() 方法只分配给 childview 50x50 的宽高。那么,这个 50x50 宽高就是 childview 实际绘制并显示到屏幕的宽高,也就是 width 和 height 值。

如果你对自定义 View 过程很熟练的话,理解这部分内容就比较轻松一些。事实上,开发过程中,getWidth() 和 getHeight() 方法用的更多一些。


作者:亦枫
链接:https://www.jianshu.com/p/51a7b9e9596b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

免责声明:文章转载自《Android 获取 View 宽高的常用正确方式,避免为零》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇pip命令提示unknow or unsupported command install解决方法通过ALT+F9关键CALL追踪注册码下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

用PHP编写Android应用程序 狼人:

Google的开源Android移动操作系统正在席卷全球智能手机市场,和苹果不一样,它对那些想将应用程序提交到iPhone App Store的开发人员有着严格的指导方针和要求,Google的Android平台非常开放,甚至还可以用PHP编写Android应用程序,Irontech创建了一个运行在Android上的PHP移植程序,结合Android的脚本层...

Android 本地化适配:RTL(right-to-left) 适配清单

本文首发自公众号:承香墨影(ID:cxmyDev),欢迎关注。 一. 序 越来越多的公司 App,都开始淘金海外,寻找更多的机会。然而海外市场千差万别,无论是市场还是用户的使用习惯,都有诸多的不同。 当你接触一款出海 App 的时候,除了需要了解海外 Google Service 的整个生态圈,还要做好不同语言的适配。语言适配最通用的做法就是根据不同系统...

adb

ADB(Android Debug Bridge) ANR(Application No Responding) adb其实就是Android Debug Bridge, Android 调试桥的缩写,adb 是一个C/S架构的命令行工具 这里介绍一些里面常用的命令: adb devices , 获取设备列表及设备状态 [xuxu:~]$ adb de...

[Android Pro] AndroidStudio IDE界面插件开发(Hello World篇)

转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53856916】 工欲善其事必先利其器,自打从Eclipse转战AndroidStudio以来,还没彻底摆脱Eclipse。打算从开发AndroidStudio插件开始,彻底摆脱Eclipse。Android...

Android之Realm详解(非原创)

文章大纲 一、Realm介绍二、Realm实战三、Realm官方文档四、项目源码下载五、参考文章 一、Realm介绍 1. 什么是Realm   Realm 是一个手机数据库,是用来替代 SQlite 的解决方案,比 SQlite 更轻量级,速度更快,因为它有一套自己的数据库搜索引擎,并且还具有很多现代数据库的优点,支持 JSON,流式 API 调用,数...

《QT Creator快速入门》第十一章(一):图形视图

一、图形视图框架结构 图形视图框架由场景QGraphicsScene、视图QGraphicsView、图形项QGraphicsItem组成,它提供了一套基于图形项模型视图编程方法。图形视图框架可以管理数量庞大的自定义2D图形项,比如要绘制上万个图形并对这些图形进行拖动、检测位置等操作的话使用图形视图框架就可以方便的管理它们。场景中可以包含各种形状的图形项,...