Android 获取 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 Studio “懒人”必备插件android layout id converter
- Android Studio第一次启动的Fetching android sdk component information的问题
- Android中DisplayMetrics 获取手机屏幕分辨率
- RecyclerView(四)设置分割线样式(Android 5.0 新特性)
- Android studio 2.0--android增量更新的那些事
- 在Android Studio中下载Android SDK的两种方式(Android Studio3.0、windows)
- 技术分享 | app自动化测试(Android)-- 属性获取与断言
- Android系统深度游
- Android中半透明Activity效果另法
- android导航设计
- Android USB 开发详解
- Android studio的错误:radle sync failed: Cause: failed to find target android-21 :
- 《Android 应用案例开发大全(第二版)》——6.7节获取第三方程序中可调用的窗口
- 《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件
- 《精通Android 5 多媒体开发》——导读
- Android清单AndroidManifest详细说明
- Android中获取资源的id和url方法总结
- Android Design Support Library(三)用CoordinatorLayout实现Toolbar隐藏和折叠
- android图像处理系列之三-- 图片色调饱和度、色相、亮度处理
- 开源分享三(炫酷的Android Loading动画)
- Android onTouchEvent, onClick及onLongClick的调用机制
- Android WebView的Js对象注入漏洞解决方案
- android源码-事件分发处理机制(下)-从信号源输入到处理完成的完整源码解读
- 获取android手机分辨率及相应的处理方式
- Android Unable to find source java class:<File>because it does not belong to any of the source dirs:
- Android获取onenet中的数据----Post( )方法---(详细)
- 【Android】使用 MediaMetadataRetriever 获取视频信息
- 允许Android对于飞行模拟器
- Android获取百度音乐下载音乐和歌词下载链接
- 【winows7+android-ndk-r9+Cygwin 】cocos2dx 2.*游戏移植Android平台完全手册
- Android Error:Execution failed for task ':app:preDebugAndroidTestBuild'. > Conflict with dependency
- 一行代码解决Android中图片加载、图片压缩、图片保存、获取缩略图、图片转换等相关问题
- Android 获取根视图
- Android 通过广播监听USB连接状态的改变