Android UI绘制流程及原理

曾经终败给现在 2023-08-17 16:29 183阅读 0赞

一、绘制流程源码路径

1、Activity加载ViewRootImpl

  1. ActivityThread.handleResumeActivity()
  2. --> WindowManagerImpl.addView(decorView, layoutParams)
  3. --> WindowManagerGlobal.addView()

2、ViewRootImpl启动View树的遍历

  1. ViewRootImpl.setView(decorView, layoutParams, parentView)
  2. -->ViewRootImpl.requestLayout()
  3. -->scheduleTraversals()
  4. -->TraversalRunnable.run()
  5. -->doTraversal()
  6. -->performTraversals()(performMeasureperformLayoutperformDraw

二、View绘制流程

1、measure

(1)MeasureSpec是什么?

重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?

留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:

  1. 00 000000 00000000 00000000 00000000

而其结构为 mode + size ,前2位为mode,而后30位为size。

==> getMode()方法(measureSpec —> mode):
  1. private static final int MODE_SHIFT = 30;
  2. // 0x3转换为二进制即为:11
  3. // 左移30位后:11000000 00000000 00000000 00000000
  4. private static final int MODE_MASK = 0x3 << MODE_SHIFT;
  5. public static int getMode(int measureSpec) {
  6. // 与MODE_MASK按位与运算后,即将低30位清零,结果为mode左移30位后的值
  7. return (measureSpec & MODE_MASK);
  8. }

getSize()方法同理。

==> makeMeasureSpec()方法(mode + size —> measureSpec):
  1. public static int makeMeasureSpec(
  2. @IntRange(from = 0,
  3. to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
  4. @MeasureSpecMode int mode) {
  5. if (sUseBrokenMakeMeasureSpec) {
  6. return size + mode;
  7. } else {
  8. return (size & ~MODE_MASK) | (mode & MODE_MASK);
  9. }
  10. }

这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:

  1. 01000000 00000000 00000000 00000000 |
  2. 00001000 00001011 11110101 10101101 =
  3. 01001000 00001011 11110101 10101101

二进制计算规则可参考:https://www.cnblogs.com/joahyau/p/6420619.html

==> 测量模式:
  1. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
  2. public static final int EXACTLY = 1 << MODE_SHIFT;
  3. public static final int AT_MOST = 2 << MODE_SHIFT;

UNSPECIFIED:父容器不对View作任何限制,系统内部使用。

EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。

AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。

(2)ViewGroup的测量流程

回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:

  1. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  2. int measureSpec;
  3. switch (rootDimension) {
  4. case ViewGroup.LayoutParams.MATCH_PARENT:
  5. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  6. break;
  7. case ViewGroup.LayoutParams.WRAP_CONTENT:
  8. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  9. break;
  10. default:
  11. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  12. break;
  13. }
  14. return measureSpec;
  15. }

match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。

往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:

  1. protected void measureChildWithMargins(View child,
  2. int parentWidthMeasureSpec, int widthUsed,
  3. int parentHeightMeasureSpec, int heightUsed) {
  4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  5. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  6. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  7. + widthUsed, lp.width);
  8. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  9. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  10. + heightUsed, lp.height);
  11. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  12. }

即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:






























childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize
match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:

  1. // 加上padding
  2. maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
  3. maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
  4. // 检查是否小于最小宽度、最小高度
  5. maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
  6. maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
  7. // 检查Drawable的最小高度和宽度
  8. final Drawable drawable = getForeground();
  9. if (drawable != null) {
  10. maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
  11. maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
  12. }
  13. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  14. resolveSizeAndState(maxHeight, heightMeasureSpec,
  15. childState << MEASURED_HEIGHT_STATE_SHIFT));

综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。

(3)View的测量流程
  1. View.performMeasure()
  2. -->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  3. -->setMeasuredDimension(int measuredWidth, int measuredHeight)
  4. -->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)

可以看到setMeasuredDimensionRaw()方法:

  1. private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
  2. // 存储测量结果
  3. mMeasuredWidth = measuredWidth;
  4. mMeasuredHeight = measuredHeight;
  5. // 设置测量完成的标志位
  6. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
  7. }

View不需要考虑子View的大小,根据内容测量得出自身大小即可。

另外,View中的onMeasure方法中调用到getDefaultSize方法:

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  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. switch (specMode) {
  10. case MeasureSpec.UNSPECIFIED:
  11. result = size;
  12. break;
  13. case MeasureSpec.AT_MOST:
  14. case MeasureSpec.EXACTLY:
  15. // 最终测量的结果都是父容器的大小
  16. result = specSize;
  17. break;
  18. }
  19. return result;
  20. }

这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。

2、layout

布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:

  1. // 实则为DecorView的left, top, right, bottom四个信息
  2. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:

  1. mLeft = left;
  2. mTop = top;
  3. mRight = right;
  4. mBottom = bottom;

所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:

  1. @Override
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3. layoutChildren(left, top, right, bottom, false /* no force left gravity */);
  4. }
  5. void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
  6. final int count = getChildCount();
  7. // 省略
  8. for (int i = 0; i < count; i++) {
  9. final View child = getChildAt(i);
  10. if (child.getVisibility() != GONE) {
  11. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  12. // 省略
  13. child.layout(childLeft, childTop, childLeft + width, childTop + height);
  14. }
  15. }
  16. }

可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。

综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。

3、draw

(1)绘制过程入口
  1. ViewRootImpl.performDraw()
  2. -->ViewRootImpl.draw()
  3. -->ViewRootImpl.drawSoftware()
  4. -->View.draw()
(2)绘制步骤

进入到View的draw方法中,可以看到以下一段注释:

  1. /*
  2. * Draw traversal performs several drawing steps which must be executed
  3. * in the appropriate order:
  4. *
  5. * 1. Draw the background
  6. * 2. If necessary, save the canvas' layers to prepare for fading
  7. * 3. Draw view's content
  8. * 4. Draw children
  9. * 5. If necessary, draw the fading edges and restore layers
  10. * 6. Draw decorations (scrollbars for instance)
  11. */

结合draw方法的源码,绘制过程的关键步骤如下:

==> 绘制背景:drawBackground(canvas)

==> 绘制自己:onDraw(canvas)

==> 绘制子view:dispatchDraw(canvas)

==> 绘制滚动条、前景等装饰:onDrawForeground(canvas)

转载于:https://www.cnblogs.com/joahyau/p/11294970.html

发表评论

表情:
评论列表 (有 0 条评论,183人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android UI渲染流程优化

    CPU和GPU CPU作为"中央处理器",除了负责逻辑运算外,还需要做内存管理,显示操作,因此随着各种复杂App的出现,其实际运算的性能会大打折扣。 设计原由: 为了

    相关 Android View绘制流程

    或许你已经看到过很多博客总结的View的绘制流程的.我这篇博客是跟着源码去看,对自己学到的知识加以印证.水平有限,仅供参考,如有错误,欢迎指正 我在之前的博客就已经说明了Ac

    相关 Android View绘制流程

    或许你已经看到过很多博客总结的View的绘制流程的.我这篇博客是跟着源码去看,对自己学到的知识加以印证.水平有限,仅供参考,如有错误,欢迎指正 我在之前的博客就已经说明了Ac

    相关 android view绘制流程

    android view是大家实现各种漂亮ui的基础,因此对于它的重要性,就可想而知了;网上关于android view分析的文章也是非常的多,之所以还写这篇文章主要还是,通过