前面的几篇文章写的都非常好的,非常的了不起,介绍的非常的详细
view的绘制:onMeasure onLayout onDraw 执行流程 【这里其实就是RootViewImpl 里面setView之后的一个流程】
这里是ViewRoot的子类,也叫实现类里面的ViewRootImpl
android怎么把view添加到窗口的:这里面进一步让我们知道整个视图的组成是有window,phoneWindow,decorview【这里面从setContentView,到addView 其实就走到setView】
这里是ViewRoot里面的
surface分析:这里面更深一步的让我们知道了activity是怎么获得一块显存,然后把视图画出来的,ViewRoot在这一步如何跟系统服务交互的,也就是怎么拿到显存的,
ViewRoot是整个显示系统中最为关键的东西,看起来这个东西好像和View有那么点关系,其实它根本和View等UI关系不大,它不过是一个Handler罢了,唯一有关系的就是它其中有一个变量为Surface类型
正是ViewRoot是一个handle类的派生,就让activityTread和System Service 进程服务搞起来了,
接下来的内容又回到view的绘制过程里面,再一次看看onMeasure详细的绘制流程,
从源码步骤来看:
1.setView()函数里面:
有行代码 requestLayout(),只是执行的第一部,
2.在requestLayout()里面:
执行了调度遍历 scheduleTraversals();
3.在调度遍历函数scheduleTraversals里面:
发送了一个空的handle消息
4.在handleMessage里面:
调用 执行遍历函数 performTraversals();到了这一步,也就是基本到了文章1里面说的绘制流程了,但是我们只是分析下onMeasure的详细过程
5.在performTraversals函数里面:这个函数太长了,
如果是初次调用:host.dispatchAttachToWindow() 这个函数的作用就是把视图与window窗口关联起来,
这里面详细有三个函数:onAttachToWindow();参数信息
listener.onViewAttachToWindow();回调
onWindowVisibilityChanged()
host.fitSystemWindow():这一步计算视图跟window之间的padding
而且这里面要非常的注意,执行了requestLayout()方法,一层一层的调用父视图
最后一步就是拿到window的宽和高
如果不是初次调用:直接拿到window的宽和高 供后面的Measure 测量用的
6.在measure里面:
有两个参数,宽和高,这个是父亲传递给孩子的规格,而这个规格的测量是调用getRootMeasure(window的期望孩子的宽高,LayoutParams的宽高)
7.onMeasure()函数:
这个韩式在measure里面调用的,只有你要求我重新绘制,或者高度和宽度变了,我才重新绘制 调用SetMeasureDimension()
8.SetMeasureDimension():这个函数非常的重要,但是里面的代码非常的简单,也是我们重载需要写的,它决定了测量之后获得的宽高
9.getDefaultSize :返回一个默认值,这个默认值调用了 makMesaureSpec 这个函数也非常的简单,size+mode,是二进制的加法
整个过程就结束了,这就是整个onMeasure的详细过程,当然这里面还涉及到非常的详细知识。
下面的文章是参考:
一、Measure本质
小福:我今天分享是的measure架构设计相关的,先问一个问题,measure的本质是什么?
小黑:这个我知道,是Android系统创建UI界面的measure、layout、draw三步骤的第一步,主要用于测量视图大小,更详细点说是把“相对值”(WRAP_CONTENT, FILL_PARENT, MATCH_PARENT)转换为具体指的过程。
小福:小黑说的对,再问一个问题,视图大小指的是什么?
小白:视图大小是在视图在屏幕上显示的大小,也就是开发的时候通过layout_width与layout_heigh设置的?
小福:小白说的只是其中一个作为开发人员的角度。Android系统设计中Canvas是无穷大的,假如一个屏幕的大小是320 * 480 ,但是layout_width="480px", layout_heigh="800px",很明显视图的宽高大于实际屏幕大小。 问题来了,视图的大小到底是屏幕上显示的大小,还是视图的实际大小(即使是超过了屏幕大小)?
小黑:具体视图显示大小是由开发人员设置,之后由我控件开发工程师在onMeasure中决定,如果向小福说的尺寸,即使超过屏幕我可以决定是width=320, heigh = 480 还是widt= 480, heigh = 800 ,决定权在我这里,一会在我分享的时候会写一个Demo来演示。 (视图根据绘制大小不同分类:内容型视图、图形型视图)
小白:Canvas是什么?小福:这个在之后分享draw过程的时候在详细讨论,可以笼统的理解为画画时使用的画布。
小福:Measure的本质是把视图布局时使用的相对值转换为具体值的过程。
二、Measure代码流程
小福:先从源码看下measure执行流程,看看这些过程中都做了些什么。以下都是android.view.ViewRootImpl.java类中的源码
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
public final class ViewRootImpl extends Handler implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
// 1 所有子视图的requestLayout方法,最总都会触发根视图此方法 public void requestLayout() { checkThread(); // 需要重新布局 mLayoutRequested = true;
scheduleTraversals(); }
// 调度遍历 public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true;
.....
// 当前类继承自Handler,发出一个空消息,目的是加入Message队列 sendEmptyMessage(DO_TRAVERSAL); } }
@Override public void handleMessage(Message msg) { switch (msg.what) {
...
case DO_TRAVERSAL: ... // 处理DO_TRAVERSAL消息 performTraversals(); ... break;
..... } }
// 执行遍历 private void performTraversals() {
final View host = mView;
int desiredWindowWidth; int desiredWindowHeight; int childWidthMeasureSpec; int childHeightMeasureSpec; ......
if (mLayoutRequested && !mStopped) { ...... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); ...... // host是一个View对象 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... }
...... } } |
注意:以上代码中getRootMeasureSpec方法可以或者跟视图中childWidthMeasureSpec与childHeightMeasureSpec,感兴趣的可以自己看下desiredWindowWidth变量的赋值其获取的是窗口的宽高。
上面的代码一共分为5个步骤 1 requestLayout() -> 2scheduleTraversals() -> 3 handleMessage() -> 4 performTraversals() ->5 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
界面中所有视图执行requestLayout,重新布局请求会逐步向上传递,最终传执行当前ViewRootImpl的requestLaout()步骤1中会执行scheduleTraversals,其中发送一个空的消息,把重新布局的请求通过Handler发送到主线程的MeassQueue等待执行(具体可以学习Handler)。因为当前ViewRootImpl是继承自Handler,所以直接查找覆写的handleMessage方法,因为传递的消息是DO_TRAVERSAL,分支调用performTraversalsperformTraversals方法中调用host.measure(childWidthMeasureSpec,childHeightMeasureSpec); 因为host是View对象所以接下来需要查看View.measure方法,才能进一步分析measure流程
接着上面的measure流程的第五步走下去,以下是android.view.View.java文件中的源码:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback, AccessibilityEventSource {
// 方法是final类型,说明不能被覆写或者重载 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果有重新请求标志,或者宽高发生改变 if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {
......
// 真正执行测量视图大小操作 // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec);
......
// 添加重新请求子视图布局标志 mPrivateFlags |= LAYOUT_REQUIRED; }
...... }
/** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view * tree. */ public void requestLayout() { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); }
// 添加重新请求布局标志 mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED;
if (mParent != null) { if (mLayoutParams != null) { mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); } if (!mParent.isLayoutRequested()) { mParent.requestLayout(); } } }
} |
上面代码的measure流程可以分为4个步骤
1 measure与requestLayout -> 2 onMeasure
measure方法是final类型,说明此方法不能被修改。其中判断条件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 进行赋值的。只要测量的宽高等发生改变都会触发第二步。执行当前的onMeasure方法,通过Hierarchy Viewer等工具可以获知根视图是FrameLayout类型(这里就不从源码验证了)
紧接着看下android.widget.FrameLayout类的onMeasure总都做了什么?
三、onMeasure方法与MeasureSpec
上面显示的代码中参数int widthMeasureSpec, int heightMeasureSpec都是通过MeasureSpec类进行统一处理。 MeasureSpec是一个android.view.View的内部类,封装了从父类传送到子类的布局要求信息。每个MeasureSpec对象描述了空间的高度或宽度。 MeasureSpec由size和mode组成。
1. MeasureSpec的方法介绍:
类名.方法名 |
解释 |
MeasureSpec.getMode(int measureSpec) |
根据提供的测量值(格式)提取模式(上述三个模式之一) |
MeasureSpec.getSize(int measureSpec) |
根据提供的测量值(格式)提取大小值 |
MeasureSpec.makeMeasureSpec(int size,int mode) |
根据提供的大小值和模式创建一个测量值(格式) |
2. MeasureSpec有三种mode,分别说明并描述模式与layout参数值的对应关系
模式 |
翻译 |
模式与Layout参数对应关系 |
模式描述 |
UNSPECIFIED |
无限制 |
parent view不约束child view的大小 |
|
AT_MOST |
最多的 |
wrap_content |
child view可以在parent view范围内取值 |
EXACTLY |
准确的 |
fill_parent(例如50dip) |
parent view为child view指定固定大小 |
3. MeasureSpec通过位运行从int类型的值中获取mode与sieze
MeasureSpec:
因为MeasureSpec类很小,而且设计的很巧妙,所以我贴出了全部的源码并进行了详细的标注。(掌握MeasureSpec的机制后会对整个Measure方法有更深刻的理解。)
[java] viewplaincopy
1. /**
2. * MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求
3. * MeasureSpec由size和mode组成。
4. * 三种Mode:
5. * 1.UNSPECIFIED
6. * 父不没有对子施加任何约束,子可以是任意大小(也就是未指定)
7. * (UNSPECIFIED在源码中的处理和EXACTLY一样。当View的宽高值设置为0的时候或者没有设置宽高时,模式为UNSPECIFIED
8. * 2.EXACTLY
9. * 父决定子的确切大小,子被限定在给定的边界里,忽略本身想要的大小。
10. * (当设置width或height为match_parent时,模式为EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的)
11. * 3.AT_MOST
12. * 子最大可以达到的指定大小
13. * (当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸)
14. *
15. * MeasureSpecs使用了二进制去减少对象的分配。
16. */
17. public class MeasureSpec {
18. // 进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和倒数第二位也就是32和31位做标志位)
19. private static final int MODE_SHIFT = 30;
20.
21. // 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0)
22. // (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)
23. private static final int MODE_MASK = 0x3 << MODE_SHIFT;
24.
25. // 0向左进位30,就是00 00000000000(00后跟30个0)
26. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
27. // 1向左进位30,就是01 00000000000(01后跟30个0)
28. public static final int EXACTLY = 1 << MODE_SHIFT;
29. // 2向左进位30,就是10 00000000000(10后跟30个0)
30. public static final int AT_MOST = 2 << MODE_SHIFT;
31.
32. /**
33. * 根据提供的size和mode得到一个详细的测量结果
34. */
35. // measureSpec = size + mode; (注意:二进制的加法,不是10进制的加法!)
36. // 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值
37. // 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100
38. public static int makeMeasureSpec(int size, int mode) {
39. return size + mode;
40. }
41.
42. /**
43. * 通过详细测量结果获得mode
44. */
45. // mode = measureSpec & MODE_MASK;
46. // MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。
47. // 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值
48. public static int getMode(int measureSpec) {
49. return (measureSpec & MODE_MASK);
50. }
51.
52. /**
53. * 通过详细测量结果获得size
54. */
55. // size = measureSpec & ~MODE_MASK;
56. // 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size
57. public static int getSize(int measureSpec) {
58. return (measureSpec & ~MODE_MASK);
59. }
60.
61. /**
62. * 重写的toString方法,打印mode和size的信息,这里省略
63. */
64. public static String toString(int measureSpec) {
65. return null;
66. }
67. }
源码中的onMeasure():
知道了widthMeasureSpec和heightMeasureSpec是什么以后,我们就可以来看onMeasure方法了:
[java] viewplaincopy
1. /**
2. * 这个方法需要被重写,应该由子类去决定测量的宽高值,
3. */
4. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
6. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
7. }
在onMeasure中只调用了setMeasuredDimension()方法,接受两个参数,这两个参数是通过getDefaultSize方法得到的,我们到源码里看看getDefaultSize究竟做了什么。
getDefaultSize():
[java] viewplaincopy
1. /**
2. * 作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使用提供的大小.否则在允许范围内可任意指定大小
3. * 第一个参数size为提供的默认大小,第二个参数为测量的大小
4. */
5. public static int getDefaultSize(int size, int measureSpec) {
6. int result = size;
7. int specMode = MeasureSpec.getMode(measureSpec);
8. int specSize = MeasureSpec.getSize(measureSpec);
9.
10. switch (specMode) {
11. // Mode = UNSPECIFIED,AT_MOST时使用提供的默认大小
12. case MeasureSpec.UNSPECIFIED:
13. result = size;
14. break;
15. case MeasureSpec.AT_MOST:
16. // Mode = EXACTLY时使用测量的大小
17. case MeasureSpec.EXACTLY:
18. result = specSize;
19. break;
20. }
21. return result;
22. }
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),这里就是获取最小宽度作为默认值,然后再根据具体的测量值和选用的模式来得到widthMeasureSpec。heightMeasureSpec同理。之后将widthMeasureSpec,heightMeasureSpec传入setMeasuredDimension()方法。
setMeasuredDimension():
[java] viewplaincopy
1. /**
2. * 这个方法必须由onMeasure(int, int)来调用,来存储测量的宽,高值。
3. */
4. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
5. mMeasuredWidth = measuredWidth;
6. mMeasuredHeight = measuredHeight;
7.
8. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
9. }
这个方法就是我们重写onMeasure()所要实现的最终目的。它的作用就是存储我们测量好的宽高值。
这下思路清晰了,现在的任务就是计算出准确的measuredWidth和heightMeasureSpec并传递进去,我们所有的测量任务就算完成了。
源码中使用的getDefaultSize()只是简单的测量了宽高值,在实际使用时需要精细、具体的测量。而具体的测量任务就交给我们在子类中重写的onMeasure方法。
measureChildren()
[java] viewplaincopy
1. /**
2. * 遍历所有的子view去测量自己(跳过GONE类型View)
3. * @param widthMeasureSpec 父视图的宽详细测量值
4. * @param heightMeasureSpec 父视图的高详细测量值
5. */
6. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
7. final int size = mChildrenCount;
8. final View[] children = mChildren;
9. for (int i = 0; i < size; ++i) {
10. final View child = children[i];
11. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
12. measureChild(child, widthMeasureSpec, heightMeasureSpec);
13. }
14. }
15. }
代码很简单,就是遍历所有的子View,如果View的状态不是GONE就调用measureChild去进行下一步的测量
measureChild()
[java] viewplaincopy
1. /**
2. * 测量单个视图,将宽高和padding加在一起后交给getChildMeasureSpec去获得最终的测量值
3. * @param child 需要测量的子视图
4. * @param parentWidthMeasureSpec 父视图的宽详细测量值
5. * @param parentHeightMeasureSpec 父视图的高详细测量值
6. */
7. protected void measureChild(View child, int parentWidthMeasureSpec,
8. int parentHeightMeasureSpec) {
9. // 取得子视图的布局参数
10. final LayoutParams lp = child.getLayoutParams();
11.
12. // 通过getChildMeasureSpec获取最终的宽高详细测量值
13. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
14. mPaddingLeft + mPaddingRight, lp.width);
15. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
16. mPaddingTop + mPaddingBottom, lp.height);
17.
18. // 将计算好的宽高详细测量值传入measure方法,完成最后的测量
19. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
20. }
getChildMeasureSpec()
[java] viewplaincopy
1. /**
2. * 在measureChildren中最难的部分:找出传递给child的MeasureSpec。
3. * 目的是结合父view的MeasureSpec与子view的LayoutParams信息去找到最好的结果
4. * (也就是说子view的确切大小由两方面共同决定:1.父view的MeasureSpec 2.子view的LayoutParams属性)
5. *
6. * @param spec 父view的详细测量值(MeasureSpec)
7. * @param padding view当前尺寸的的内边距和外边距(padding,margin)
8. * @param childDimension child在当前尺寸下的布局参数宽高值(LayoutParam.width,height)
9. */
10. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
11. //父view的模式和大小
12. int specMode = MeasureSpec.getMode(spec);
13. int specSize = MeasureSpec.getSize(spec);
14.
15. //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)
16. int size = Math.max(0, specSize - padding);
17.
18. //子view想要的实际大小和模式(需要计算)
19. int resultSize = 0;
20. int resultMode = 0;
21.
22. //通过1.父view的MeasureSpec 2.子view的LayoutParams属性这两点来确定子view的大小
23. switch (specMode) {
24. // 当父view的模式为EXACITY时,父view强加给子view确切的值
25. case MeasureSpec.EXACTLY:
26. // 当子view的LayoutParams>0也就是有确切的值
27. if (childDimension >= 0) {
28. //子view大小为子自身所赋的值,模式大小为EXACTLY
29. resultSize = childDimension;
30. resultMode = MeasureSpec.EXACTLY;
31. // 当子view的LayoutParams为MATCH_PARENT时(-1)
32. } else if (childDimension == LayoutParams.MATCH_PARENT) {
33. //子view大小为父view大小,模式为EXACTLY
34. resultSize = size;
35. resultMode = MeasureSpec.EXACTLY;
36. // 当子view的LayoutParams为WRAP_CONTENT时(-2)
37. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
38. //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST
39. resultSize = size;
40. resultMode = MeasureSpec.AT_MOST;
41. }
42. break;
43.
44. // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。
45. case MeasureSpec.AT_MOST:
46. // 道理同上
47. if (childDimension >= 0) {
48. resultSize = childDimension;
49. resultMode = MeasureSpec.EXACTLY;
50. } else if (childDimension == LayoutParams.MATCH_PARENT) {
51. resultSize = size;
52. resultMode = MeasureSpec.AT_MOST;
53. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
54. resultSize = size;
55. resultMode = MeasureSpec.AT_MOST;
56. }
57. break;
58.
59. // 当父view的模式为UNSPECIFIED时,子view为想要的值
60. case MeasureSpec.UNSPECIFIED:
61. if (childDimension >= 0) {
62. // 子view大小为子自身所赋的值
63. resultSize = childDimension;
64. resultMode = MeasureSpec.EXACTLY;
65. } else if (childDimension == LayoutParams.MATCH_PARENT) {
66. // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0
67. resultSize = 0;
68. resultMode = MeasureSpec.UNSPECIFIED;
69. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
70. // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0
71. resultSize = 0;
72. resultMode = MeasureSpec.UNSPECIFIED;
73. }
74. break;
75. }
76. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
77. }
到这一步整个测量基本就结束了:
view.getWidth()与view.getMeasuredWidth()
getMeasuredWidth是视图onMeasure指定的宽度(可以笼统的理解为视图内容区域的大小,虽然不严谨但是系统提供的布局控件都是这样,仅在自定义视图中因为覆写onMeasure可以忽略layout_width,layout_heigh随意指定其宽高),而getWidth是视图父视图指定当前视图可以在屏幕上显示的区域。
/**
* Like {@link#getMeasuredWidthAndState()}, but only returns the
* raw width component (thatis the result is masked by
* {@link #MEASURED_SIZE_MASK}).
*
* @return The raw measuredwidth of this view.
*/
public final int getMeasuredWidth() {
// 直接返回mMeasuredWidth与后者相与清理掉其他开关获取真是measure大小
returnmMeasuredWidth & MEASURED_SIZE_MASK;
}
2.如何正确获取view的宽高呢,这个经常被面试官问了,如果你直接说view.getWidth,那肯定是错的,返回的是0,
通过前面的许多的文章分析,这个为什么是0的结果就非常的好说了,执行onCreate函数的时候,执行setContentView()这个过程,这个过程涉及到很多很多过程,但是不论怎么样,他的返回的结果就是给你显卡,然后让你自己绘制,只有当显示给你了,你才能去执行setView了,
我们在activity的生命周期中知道,视图是什么时候可见,什么时候可以操作的呢,onCreate()这个生命周期里面什么都干不了,不可见不可操作,可以肯定的是onMeasure函数在onCreate函数执行完之后才会执行,onStart生命周期里面是可见的,但是这个过程里面视图到底绘制好了吗 其实应该是绘制好了,onResume里面是可见可操作,有的人说在这个里面拿不就是了,理论上没有错的,就需要分析下这个原因了,【先给出结论:这个时间点真的不知道在哪里,到底什么时候绘制好了,】
我们都知道在onCreate()里面获取控件的高度是0,这是为什么呢?我们来看一下示例:
首先我们自己写一个控件,这个控件非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class MyImageView extends ImageView { public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); } public MyImageView(Context context) { super(context); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); System.out.println("onMeasure 我被调用了"+System.currentTimeMillis()); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); System.out.println("onDraw 我被调用了"+System.currentTimeMillis()); } } |
布局文件:
1 2 3 4 5 |
<com.test.MyImageView android:id="@+id/imageview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/test" /> |
测试的Activity的onCreate():
1 2 3 4 5 6 |
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("执行完毕.."+System.currentTimeMillis()); } |
现在我们现在来看一下结果:
说明等onCreate方法执行完了,我们定义的控件才会被度量(measure),所以我们在onCreate方法里面通过view.getHeight()获取控件的高度或者宽度肯定是0,因为它自己还没有被度量,也就是说他自己都不知道自己有多高,而你这时候去获取它的尺寸,肯定是不行的.
有如下两种方法可以解决这个问题:
----------------------------------
方法一:使用view的measure方法。
------------------------------
优点:可以立即获得宽和高
缺点:人为的多了一次测量过程
这种方法适用于需要在onCreate完成之前就获得一个view的宽和高的情况。
比如获得一个LinearLayout宽和高
1 2 3 4 5 6 7 8 9 10 |
//宽 public int getViewWidth(LinearLayout view){ view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); return view.getMeasuredWidth(); } //高 public int getViewHeight(LinearLayout view){ view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); return view.getMeasuredHeight(); } |
这种方法的原理是直接调用一个view或者viewgroup的measure方法去测量,测量之后该view的getMeasuredHeight()就会返回刚才测量所得的高,getMeasuredWidth返回测量所得宽。本来在布局加载的过程中,view的measure方法一定会被系统调用,但这发生在我们所不知道的某个时间点,为了在这之前提前得到测量结果,我们主动调用measure方法,但是这样做的好处是可以立即获得宽和高,坏处是多了一次测量过程。
至于为什么参数是LayoutParams.WRAP_CONTENT,那是因为我假设这个view的layout_width和layout_height为wrap_content,因为如果为一个确切的值,还有必要测量吗?
-------------------------------------------------------------------
方法二:布局监听类ViewTreeObserver的OnGlobalLayoutListener
-------------------------------------------------------------
当一个view的布局加载完成或者布局发生改变时OnGlobalLayoutListener可以监听到,利用这点我们可以在布局加载完成的瞬间获得一个view的宽高。
1 2 3 4 5 6 7 8 9 10 11 |
int mHeaderViewHeight; mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() {
mHeaderViewHeight = mHeaderView.getHeight(); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); |
这种方法无法像第一种方法那样通过一个函数返回值,因为他是基于listener的,OnGlobalLayoutListener的onGlobalLayout被回调之前是没有值的。由于布局状态可能会发生多次改变,因此OnGlobalLayoutListener的onGlobalLayout可能被回调多次,所以我们在第一次获得值之后就将listener注销掉。
优点:不需要额外的测量过程
缺点:只有在布局加载完成后,才能得到宽和高
其实在activity的onResume中可以直接调用view.getWidth获得宽,那是不是第二种方法就失去意义了呢?
当然不是,如果我们自定义一个view,需要在view的内部获得某个子view的宽和高,而view本身又没有onResume这样的生命周期方法,这时OnGlobalLayoutListener的onGlobalLayout就起作用了,可以认为onGlobalLayout就是相当于一个view的生命周期。