背景

有时我们会有基于这样的需求,当Activity创建时,调用View.getWidth、View.getHeight()、View.getMeasuredWidth() 、View.getgetMeasuredHeight()来获得某个view的宽度或高度,然后进行相应的操作,但是我们在onCreate()、onStart()、onResume()中获取View的大小,获取到的值都是0,这是应为当前activity所代表的界面还没显示出来没有添加到WindowPhone的DecorView上或要获取的view没有被添加到DecorView上或者该View的visibility属性为gone 或者该view的width或height真的为0 ,所以只有上述条件都不成立时才能得到非0的width和height。因为View的measure过程和Activity的生命周期不是同步进行的,不能保证在Activity的onCreate()、onStart()、onResume()方法执行时View已经测量完毕,所以不能获取到正确的宽高,也就是onCreate()、onStart()、onResume()等方法执行完了,我们定义的控件才会被测量,所以我们在onCreate()、onStart()、onResume()里面通过 view.getHeight() 获取控件的高度肯定是0,因为它还没有被测量,View的绘制工程还未完成,和在onCreate中弹出Dialog或者PopupWindow会报一个Activity not running原理类似。

这需要我们去了解一下View的绘制流程,View 需要onMeasure、onLayout、onDraw 三个过程才会真正画出来,执行onMeasure后可以得到 mMeasuredWidth、mMeasuredHeight, 执行onLayout 后可以得到 mLeft、mTop、mRight、mBottom 四个值,getWidth和getHeight就是通过这四个值计算的,如果我们在onCreate() 或 onResume() 中获取View 的宽高值,由于此时View还没measure\layout\draw,所以此时获取的就是0。在measure、layout、draw的每个过程都有方法获取View 的宽高值,下面一一介绍。

获取宽高方式

(1)通过onWindowFocusChanged方法

当整个DecorView已经绘制完成,Activty已经要显示出来的时候,就会回调Activity#onWindowFocusChanged。Activity的窗口得到焦点时,View已经初始化完成,此时获取到的View的宽高是准确的,此方法会被多次调用,当Activity的窗口得到/失去焦点时均会被调用一次,同样的Dialog和PopupWindow也可以在这里弹出,需要注意的是这个方法会调用多次,当hasFocus为true时,才可进行相应的操作

public class GetHeightSampleActivity extends AppCompatActivity {TextView textView;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_get_height);textView = findViewById(R.id.tv);}@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);if (hasFocus) {Log.w("tv_width", "" + textView.getWidth());Log.w("tv_height", "" + textView.getHeight());}}
}

(2)通过LayoutParams获取

对于在XML文件里设置了具体宽高的View可以通过view.getLayoutParams().height/width获取到宽高。

优点是能及时获取到,且操作简单;缺点是不够通用,没有设置具体宽高的获取到的值就是0了。

(3)调用View的measure方法

我们可以直接调用 View.measure 来获取 mMeasuredWidth与mMeasuredHeight,但是这个测量宽高可能与实际宽高不一致。
getMeasuredWidth() 与 getMeasuredHeight() 获取的是mMeasuredWidth与mMeasuredHeight,这是在 measure 过程计算出来的测量宽高,而 getWidth() 与 getHeight() 是在 layout 过程之后计算出来的宽高。我们知道 View 的宽高是由 View 本身和 parent 容器共同决定的。mMeasuredWidth与mMeasuredHeight是View 本身的size,但是如果 parent 容器 没有足够大,View就不得不降低自己的尺寸。比如,View 通过自身 measure() 方法向 parent 请求 100x100 的宽高,那么这个宽高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 阶段,通过 childview.layout() 方法只分配给 childview 50x50 的宽高。那么,这个 50x50 宽高就是 childview 实际绘制并显示到屏幕的宽高,也就是 width 和 height 值。

public class MainActivity extends AppCompatActivity {private View view;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);view = findViewById(R.id.view);view.measure(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);int width =  view.getMeasuredWidth();Log.i("TAG", "width=" +width);}
}

我们在 onCreate() 中要用到控件的宽高的时候可以主动去为它测量,也就是直接调用一个 view 或者 viewgroup 的 measure() 去测量。

测量之后该 view 的 getMeasuredHeight() 就会返回刚才测量所得的高,getMeasuredWidth() 返回测量所得宽。

本来在布局加载的过程中,view的 measure方法一定会被系统调用(在onResume() 中已经调用了 measure方法),但这发生在我们所不知道的某个时间点,为了在这之前提前得到测量结果,我们主动调用 measure方法,但是这样做的好处是可以立即获得宽和高,坏处是多了一次测量过程。

对于宽高设为具体数值或wrap_content的控件,我们都可以手动构造MeasureSpec,而match_parent的情况是做不到的,match_parent无法measure出具体的宽/高,原因很简单,根据View的measure过程,构造此种MeasureSpec需要父view的size,即父容器的剩余空间,
所以我们需要在measure时传递正确的父View的Size,而此时我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。

对于设置了具体数值宽高的(比如都是100px),我们可以这样构造MeasureSpec:

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);

对于设置成wrap_content的:

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOTST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);

注意到(1<<30)-1,通过分析MeasureSpec可知,View的尺寸使用30位二进制表示,也就是说最大是30个1(即2^30-1),也就是(1<<30)-1,在最大化模式下,我们用View理论上能支持的最大值去构造MeasureSpec是合理的。

关于View的measure,网上有两个错误的用法。
为什么说是错误的,首先起违背了系统的内部实现规范,因为无法通过错误的MeasureSpec去得出合法的SpecMode,从而导致measure过程出错,其次不能保证一定能measure出正确的结果。
第一种错误用法:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mView.measure(widthMeasureSpec, heightMeasureSpec);

第二种错误用法:

mView.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

但是这种测试可以得到宽高。。。待研究。

优点也是可以立即获取到宽高;缺点是无法解决match_parent的情况,该方法测量的宽度和高度可能与视图绘制完成后的真实的宽度和高度不一致。

(4)OnGlobalLayoutListener获取

另一种方法是利用 ViewTreeObserver 的 OnGlobalLayoutListener 来测量

在布局发生改变或者某个视图的可视状态发生改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {view.getViewTreeObserver().removeOnGlobalLayoutListener(this);final int w = view.getMeasuredWidth();final int h = view.getMeasuredHeight();}
});

(5)OnPreDrawListener获取

在视图将要绘制时调用该监听事件,会被调用多次,因此获取到视图的宽度和高度后要移除该监听事件。这同样是 ViewTreeObserver 的接口。
OnPreDrawListener是在draw之前的回调,此时已经 layout 过,可以获取到 View 的宽高值。OnPreDrawListener还可以控制绘制流程,返回false的时候就取消当前绘制流程,View会再schedule下一次绘制:

view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic boolean onPreDraw() {view.getViewTreeObserver().removeOnPreDrawListener(this);int w = view.getWidth();int h = view.getHeight();return true;}}
);

(6)OnLayoutChangeListener获取

在视图的 layout 改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

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

从中可以发现在view本身的layout时才会回调OnLayoutChangeListener。而对于OnGlobalLayoutListener, 不管view本身或其父view的layout时都会回调。

(7)重写View的onSizeChanged()

在视图的大小发生改变时调用该方法,会被多次调用,因此获取到宽度和高度后需要考虑禁用掉代码。

该实现方法需要继承 View,且多次被调用,不建议使用。

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);Log.i("TAG", "width = " + getWidth() + "height = " + getHeight());
}

(8)重写View的onLayout()

该方法会被多次调用,获取到宽度和高度后需要考虑禁用掉代码。
对于自定义的View,可以重写 onLayout() 并在此函数中获取宽高,且多次被调用,不建议使用。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);Log.i("TAG", "width = " + getWidth() + "height = " + getHeight());}

补充一点,getMeasuredWidth() 与 getWidth() 或者 getMeasuredHeight() 与 getHeight() 的区别。measuredWidth 与 width 分别对应于视图绘制 的 measure 与 layout 阶段。View 的宽高是由 View 本身和 parent 容器共同决定的,要知道有这个 MeasureSpec 类的存在。
比如,View 通过自身 measure() 方法向 parent 请求 100x100 的宽高,那么这个宽高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 阶段,通过 childview.layout() 方法只分配给 childview 50x50 的宽高。那么,这个 50x50 宽高就是 childview 实际绘制并显示到屏幕的宽高,也就是 width 和 height 值。

(9)使用View.post()方法

这里的view可以是你需要获取宽高的View。要注意的是view要执行此方法必须保证它已经attached到了window上,因此在此之前是不能调用这个方法的。

而通过view.post()在主线程的消息队列尾部插入了一个消息,也就是说执行获取宽高的操作被延后了,并且能够保证Measure操作在此之前,所以就能够在这里获取到正确的宽高了。
Runnable 对象中的方法会在 View 的 measure()、layout() 等事件完成后触发。

UI 事件队列会按顺序处理事件,在 setContentView() 被调用后,事件队列中会包含一个要求重新 layout 的 message,所以任何 post 到队列中的 Runnable 对象都会在 Layout 发生变化后执行。

该方法只会执行一次,且逻辑简单,建议使用。

此方法优点是保证获取到的宽高是准确的;
缺点是不能及时获取到,实际上还是把操作延后了,需要在Runnable里再执行相应回调。

view.post(new Runnable() {@Overridepublic void run() {Log.i("TAG", "width = " + view.getWidth() +"height = " + view.getHeight());}
});

(10)ViewCompat.isLaidOut(view)

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

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

本文是学习记录,仅供参考

Android获取View宽高的常见方式相关推荐

  1. java中的onresume_android onCreate onResume中获取 View 宽高为0分析

    1.问题测试 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_par ...

  2. Android获取屏幕宽高,状态栏宽高,actionbar宽高,layout宽高,导航栏高度的方法汇总

    看这个博客你可以知道 获取屏幕宽高,状态栏宽高,actionbar宽高,layout宽高,导航栏(虚拟按键栏)高度的方法 目录顺序为 代码测试的机型 状态栏高度 actionbar高度 屏幕高度 导航 ...

  3. android获取屏幕宽高与获取控件宽高

    1.获取屏幕宽高 方法1: int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px ...

  4. android获取该控件在屏幕,android获取屏幕宽高与获取控件宽高(三种方法)

    1.获取屏幕宽高 方法1: int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); // 屏幕宽(像素,如:480px ...

  5. 关于Android获取屏幕宽高、dp、sp、px之间的转化

    开发过程中,动态创建布局,或者自定义view,少不了需要获取屏幕宽高,这里的宽高指手机屏幕的分辨率,单位是px,而我们在布局文件中用到的空间宽高单位是dp,字体用的是sp. 这几个计量单位之间,是有关 ...

  6. Android 获取视频宽高

    关于获取视频宽高 最近一次需求是上传视频,并根据上传视频的宽高展示横屏或者竖屏的video控件,最初是用MediaMetadataRetriever类来获取视频的宽高 // An highlighte ...

  7. Android 获取屏幕宽高的正确姿势

    前言 在开发时,我们经常需要根据屏幕的宽高来进行对view的适配,无论是自定义view还是andorid自带的一些控件,比如说需要占当前屏幕高度的30%,就需要获取到屏幕的宽高,但在获取宽高时我遇到了 ...

  8. 获取屏幕的宽高 android,Android获取屏幕宽高的方法

    1. 实现代码 private intmWidth;private intmHeight; @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)public v ...

  9. android 自定义控件的宽高_巧用Handler获取View控件信息

    众所周知,在Android实际开发中,对于某些复杂多变的情况,控件的位置摆放.大小控制并非是xml类型的layout文件完全可以搞定的.此时,我们通常会使用Java代码来通过动态计算,将指定的控件摆放 ...

最新文章

  1. Java的字符串常量池
  2. 居住7年未交一分钱天然气使用费 女房主替租户偿还近4万元欠款
  3. Windows 驱动开发资源链接
  4. antd table 时间搜索_antd table按表格里的日期去排序操作
  5. 多线程 调用多线程的方法 Runtime与ProcessBuilder
  6. PyTorch框架学习八——PyTorch数据读取机制(简述)
  7. 多益网络2014校招的一道笔试题---左旋字符串
  8. 如何查询以太信道接口_浅谈百兆千兆以太网物理层
  9. vue 指令 v-bind
  10. [转载] numpy.inf
  11. Putty server refused our key的解决方法
  12. 基于马尔可夫随机场的深度估计
  13. 横向滑动视图HorizontalScrollView精炼详解
  14. 【音视频—基础】分辨率、码率和帧率
  15. vue3 + vite中按需使用ace-builds实现编辑器
  16. Going Deeper into Regression Analysis with Assumptions, Plots Solutions
  17. 仓储系统主要注意事项
  18. 夏雨老师告诉您学习平面设计到底好不好呢?
  19. Huffman树(哈夫曼树)
  20. CRM系统部署模式有哪些

热门文章

  1. 北京生存日记 Day one
  2. C语言的运行环境及运行方法(例子)
  3. 张爱玲《色戒》-谈女人-读书笔记
  4. Android ndk:/Users/Library/Android/sdk/ndk/21.1.6352462 did not have a source.properties file bug问题
  5. Mysql集群配置(回顾)
  6. Scrum: 增量和迭代开发有什么区别?Scrum: Incremental vs Iterative
  7. 十分钟学会在phpstudy上快速搭建环境,开发php项目
  8. matlab三维路径规划,【路径规划】基于A星算法的三维路径规划matlab源码
  9. 一个人的最高境界两个字!{一}第一个字:“度” {二}第二个字:“给”
  10. 如何给普通用户设置 sudo 权限