一、view树的绘制流程

measure--->layout--->draw

measure

1、ViewGroup.LayoutParams 指定部件的长宽

2、MeasureSpec 32位的int值 前两位代表模式 后30位测量规格的大小

layout

draw

invalidate()请求android系统 如果大小没有发生变化 就不会调用layout放置这个过程

requestLayout() 当布局发生变化时 希望重新测量尺寸大小 就会触发 measure和layout 但不会调用draw方法

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

各步骤的主要工作:

  • OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

  • OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

  • OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

OnMeasure

View树的绘制是从ViewRoot的performTraversals()方法开始,这个方法的主要作用是判断是否重新measure、是否重新layout、是否重新draw。

  1. private void performTraversals() {
  2. ......
  3. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  4. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  5. ......
  6. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  7. ......
  8. mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
  9. ......
  10. mView.draw(canvas);
  11. ......
  12. }
  13.  
  14. private int getRootMeasureSpec(int windowSize, int rootDimension) {
  15. int measureSpec;
  16. switch (rootDimension) {
  17. case ViewGroup.LayoutParams.MATCH_PARENT:
  18. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  19. break;
  20. case ViewGroup.LayoutParams.WRAP_CONTENT:
  21. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  22. break;
  23. default:
  24. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  25. break;
  26. }
  27. return measureSpec;
  28. }
  29.  
  30. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  31. if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
  32. widthMeasureSpec != mOldWidthMeasureSpec ||
  33. heightMeasureSpec != mOldHeightMeasureSpec) {
  34. mPrivateFlags &= ~MEASURED_DIMENSION_SET;
  35. if (ViewDebug.TRACE_HIERARCHY) {
  36. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
  37. }
  38. onMeasure(widthMeasureSpec, heightMeasureSpec);
  39. if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
  40. throw new IllegalStateException("onMeasure() did not set the"
  41. + " measured dimension by calling"
  42. + " setMeasuredDimension()");
  43. }
  44. mPrivateFlags |= LAYOUT_REQUIRED;
  45. }
  46. mOldWidthMeasureSpec = widthMeasureSpec;
  47. mOldHeightMeasureSpec = heightMeasureSpec;
  48.  
  49. //View的onMeasure默认实现方法,设置View的长和宽;如果写死,则外边变化时界面不变化
  50. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  51. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  52. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  53. }
  54.  
  55. public static int getDefaultSize(int size, int measureSpec) {
  56. int result = size;
  57. int specMode = MeasureSpec.getMode(measureSpec);
  58. int specSize = MeasureSpec.getSize(measureSpec);
  59. switch (specMode) {
  60. case MeasureSpec.UNSPECIFIED:
  61. result = size;
  62. break;
  63. case MeasureSpec.AT_MOST:
  64. case MeasureSpec.EXACTLY:
  65. result = specSize;
  66. break;
  67. }
  68. return result;
  69. }

如果是ViewGroup,还应该进行嵌套测量

  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  2. final int size = mChildrenCount;
  3. final View[] children = mChildren;
  4. for (int i = 0; i < size; ++i) {
  5. final View child = children[i];
  6. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
  7. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  8. }
  9. }
  10. }
  11.  
  12. protected void measureChild(View child, int parentWidthMeasureSpec,
  13. int parentHeightMeasureSpec) {
  14. final LayoutParams lp = child.getLayoutParams();
  15. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  16. mPaddingLeft + mPaddingRight, lp.width);
  17. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  18. mPaddingTop + mPaddingBottom, lp.height);
  19. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  20. }
  21.  
  22. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  23. int specMode = MeasureSpec.getMode(spec);
  24. int specSize = MeasureSpec.getSize(spec);
  25. int size = Math.max(0, specSize - padding);
  26. int resultSize = 0;
  27. int resultMode = 0;
  28. switch (specMode) {
  29. case MeasureSpec.EXACTLY:
  30. if (childDimension >= 0) {
  31. resultSize = childDimension;
  32. resultMode = MeasureSpec.EXACTLY;
  33. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  34. resultSize = size;
  35. resultMode = MeasureSpec.EXACTLY;
  36. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  37. // Child wants to determine its own size. It can't be
  38. // bigger than us.
  39. resultSize = size;
  40. resultMode = MeasureSpec.AT_MOST;
  41. }
  42. break;
  43. ......
  44. }
  45. //将mode与size通过MeasureSpec方法整合为32位整数返回
  46. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  47. }

MeasureSpec 的值由specSize和specMode共同组成的,高2位代表specMode,低30代表spceSize,其中specSize记录的是大小,specMode记录的是规格。

specMode一共有三种类型:

  1. EXACTLY 
    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  2. AT_MOST 
    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  3. UNSPECIFIED 
    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见。

每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

OnLayout

测量完各个组件的大小之后,就可以排列他们的位置了。

  1. private void performTraversals() {
  2. ......
  3. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  4. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  5. ......
  6. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  7. ......
  8. mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
  9. ......
  10. mView.draw(canvas);
  11. ......
  12. }
  13.  
  14. public void layout(int l, int t, int r, int b) {
  15. int oldL = mLeft;
  16. int oldT = mTop;
  17. int oldB = mBottom;
  18. int oldR = mRight;
  19. boolean changed = setFrame(l, t, r, b);//视图大小是否发生变化
  20. if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
  21. if (ViewDebug.TRACE_HIERARCHY) {
  22. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
  23. }
  24. onLayout(changed, l, t, r, b);
  25. mPrivateFlags &= ~LAYOUT_REQUIRED;
  26. if (mOnLayoutChangeListeners != null) {
  27. ArrayList<OnLayoutChangeListener> listenersCopy =
  28. (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
  29. int numListeners = listenersCopy.size();
  30. for (int i = 0; i < numListeners; ++i) {
  31. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  32. }
  33. }
  34. }
  35. mPrivateFlags &= ~FORCE_LAYOUT;
  36. }

View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

  1. @Override
  2. /**
  3. *ViewGroupd的onLayout方法
  4. */
  5. protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

然而,ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

下面代码以LinearLayout举例:

  1. public class LinearLayout extends ViewGroup {
  2. @Override
  3. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  4. if (mOrientation == VERTICAL) {
  5. layoutVertical(l, t, r, b);
  6. } else {
  7. layoutHorizontal(l, t, r, b);
  8. }
  9. }
  10. }
  11.  
  12. void layoutVertical(int left, int top, int right, int bottom) {
  13. final int paddingLeft = mPaddingLeft;
  14. int childTop;
  15. int childLeft;
  16. final int width = right - left;
  17. int childRight = width - mPaddingRight;
  18. int childSpace = width - paddingLeft - mPaddingRight;
  19. final int count = getVirtualChildCount();
  20. final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
  21. final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
  22. switch (majorGravity) {
  23. case Gravity.BOTTOM:
  24. childTop = mPaddingTop + bottom - top - mTotalLength;
  25. break;
  26. case Gravity.CENTER_VERTICAL:
  27. childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
  28. break;
  29. case Gravity.TOP:
  30. default:
  31. childTop = mPaddingTop;
  32. break;
  33. }
  34. for (int i = 0; i < count; i++) {
  35. final View child = getVirtualChildAt(i);
  36. if (child == null) {
  37. childTop += measureNullChild(i);
  38. } else if (child.getVisibility() != GONE) {
  39. final int childWidth = child.getMeasuredWidth();
  40. final int childHeight = child.getMeasuredHeight();
  41. final LinearLayout.LayoutParams lp =
  42. (LinearLayout.LayoutParams) child.getLayoutParams();
  43. int gravity = lp.gravity;
  44. if (gravity < 0) {
  45. gravity = minorGravity;
  46. }
  47. final int layoutDirection = getLayoutDirection();
  48. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
  49. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  50. case Gravity.CENTER_HORIZONTAL:
  51. childLeft = paddingLeft + ((childSpace - childWidth) / 2)
  52. + lp.leftMargin - lp.rightMargin;
  53. break;
  54. case Gravity.RIGHT:
  55. childLeft = childRight - childWidth - lp.rightMargin;
  56. break;
  57. case Gravity.LEFT:
  58. default:
  59. childLeft = paddingLeft + lp.leftMargin;
  60. break;
  61. }
  62. if (hasDividerBeforeChildAt(i)) {
  63. childTop += mDividerHeight;
  64. }
  65. childTop += lp.topMargin;
  66. setChildFrame(child, childLeft, childTop + getLocationOffset(child),
  67. childWidth, childHeight);
  68. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
  69. i += getChildrenSkipCount(child, i);
  70. }
  71. }
  72. }

到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:

  1. public final int getMeasuredWidth() {
  2. return mMeasuredWidth & MEASURED_SIZE_MASK;
  3. }
  4.  
  5. public final int getMeasuredHeight() {
  6. return mMeasuredHeight & MEASURED_SIZE_MASK;
  7. }
  8.  
  9. public final int getWidth() {
  10. return mRight - mLeft;
  11. }
  12.  
  13. public final int getHeight() {
  14. return mBottom - mTop;
  15. }
  16.  
  17. public final int getLeft() {
  18. return mLeft;
  19. }
  20.  
  21. public final int getRight() {
  22. return mRight;
  23. }
  24.  
  25. public final int getTop() {
  26. return mTop;
  27. }
  28.  
  29. public final int getBottom() {
  30. return mBottom;
  31. }

OnDraw

  1. public void draw(Canvas canvas) {
  2. if (ViewDebug.TRACE_HIERARCHY) {
  3. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
  4. }
  5. final int privateFlags = mPrivateFlags;
  6. final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
  7. (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
  8. mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
  9. // Step 1, draw the background, if needed
  10. int saveCount;
  11. if (!dirtyOpaque) {
  12. final Drawable background = mBGDrawable;
  13. if (background != null) {
  14. final int scrollX = mScrollX;
  15. final int scrollY = mScrollY;
  16. if (mBackgroundSizeChanged) {
  17. background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
  18. mBackgroundSizeChanged = false;
  19. }
  20. if ((scrollX | scrollY) == 0) {
  21. background.draw(canvas);
  22. } else {
  23. canvas.translate(scrollX, scrollY);
  24. background.draw(canvas);
  25. canvas.translate(-scrollX, -scrollY);
  26. }
  27. }
  28. }
  29. final int viewFlags = mViewFlags;
  30. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
  31. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
  32. if (!verticalEdges && !horizontalEdges) {
  33. // Step 3, draw the content
  34. if (!dirtyOpaque) onDraw(canvas);
  35. // Step 4, draw the children
  36. dispatchDraw(canvas);
  37. // Step 6, draw decorations (scrollbars)
  38. onDrawScrollBars(canvas);
  39. // we're done...
  40. return;
  41. }
  42. }

android view绘制流程 面试的更多相关文章

  1. Android View 绘制流程(Draw) 完全解析

    前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程——绘制流程.测量流程决定了View的大小,布局流程决定了View的位 ...

  2. Android View绘制流程

    框架分析 在之前的下拉刷新中,小结过触屏消息先到WindowManagerService(Wms)然后顺次传递给ViewRoot(派生自Handler),经decor view到Activity再传递 ...

  3. Android View 绘制流程

    Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设 ...

  4. Android View 布局流程(Layout)完全解析

    前言 上一篇文章,笔者详细讲述了View三大工作流程的第一个,Measure流程,如果对测量流程还不熟悉的读者可以参考一下上一篇文章.测量流程主要是对View树进行测量,获取每一个View的测量宽高, ...

  5. Android View 测量流程(Measure)完全解析

    前言 上一篇文章,笔者主要讲述了DecorView以及ViewRootImpl相关的作用,这里回顾一下上一章所说的内容:DecorView是视图的顶级View,我们添加的布局文件是它的一个子布局,而V ...

  6. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. 【朝花夕拾】Android自定义View篇之(一)View绘制流程

    前言 转载请申明转自[https://www.cnblogs.com/andy-songwei/p/10955062.html]谢谢! 自定义View.多线程.网络,被认为是Android开发者必须牢 ...

  8. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

  9. 简单研究Android View绘制一 测量过程

    2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Dr ...

随机推荐

  1. MT【232】展开式中的系数

    $(1+x+x^2+\cdots+x^{100})^3$展开式中$x^{150}$前的系数为_____ 解答:$(1+x+x^2+\cdots+x^{100})^3=(1-x^{101})^3\sum ...

  2. 【ZOJ2276】Lara Croft(bfs)

    BUPT2017 wintertraining(16) #4 D ZOJ - 2276 题意 n个数字绕成环,有两个指示数字的方块,每次可以顺时针或逆时针移动其中一个,步数是它当前位置的数字a[i], ...

  3. 自学Linux Shell16.3-函数递归+创建库

    点击返回 自学Linux命令行与Shell脚本之路 16.3-函数递归+创建库 1. 函数递归 递归调用函数是指函数调用自身进行求解. 通常,递归函数有基值,函数最终递推到达该值. 许多高级数学算法使 ...

  4. 一篇写得很好的关于lct的博客

    连接 orz orz

  5. Windows下查看游戏服务器的IP地址

    在任务管理器中查看进程的PID 在cmd中使用netstat -aon,找到PID对应的IP地址

  6. 【模板】Splay

    Splay 均摊复杂度证明见此处 \(\rightarrow\) 链接 代码如下 #include <bits/stdc++.h> using namespace std; const i ...

  7. MATLAB:图像水平、垂直、水平垂直镜像、转置、旋转变换(flipdim、mirror、transp、imrotate函数)

    1.原图像经水平.垂直.水平垂直镜像设置通过mirror函数实现: close all; %关闭当前所有图形窗口,清空工作空间变量,清除工作空间所有变量 clear all; clc; I=imrea ...

  8. MATLAB:图像乘除法运算(immultiply、imdivide函数),同时比较双精度类型图像运算结果

    图像乘除法运算涉及到immultiply.imdivide函数,实现代码如下: 1.图像之间的乘法运算 close all; %关闭当前所有图形窗口,清空工作空间变量,清除工作空间所有变量 clear ...

  9. myEclipse全局搜索时报错

    1.全局搜索时出错. 大家都知道,Eclipse可以用Ctrl+ H 进行全局搜索,但今天搜索时出现了一个问题: Problem Occurred ‘File Search’ has encounte ...

  10. 在IIS6中配置html文件以ASPX方式工作

    在IIS6中配置html文件以ASPX方式工作 由于IIS6的安全不断提高,如果你需要设置html文件以ASPX文件方式被执行.仅仅设置应用程序映射是不够的,还 需要修改一些其他设置. 如果你只修改了 ...