Android View绘制的整体流程

在正式分析View的绘制流程之前,我们要先了解一下Android的UI管理系统的层级关系。View层级关系图
PhoneWindow是Android系统中最基本的窗口系统,每个Activity会创建一个。PhoneWindow是Activity和View系统相互交互的接口。DecorView本质是一个FrameLayout,是Acitivty中所有View的祖先。

1.绘制的整体流程

当一个应用启动时,会启动一个主Activity,Android系统会根据Aactivity的布局来对它进行绘制。绘制会从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。视图绘制的过程可以分为三个步骤,分别是测量(Maesure)、布局(layout)、绘制(Draw)performTraverslas()核心代码:

 private void performTraversals() {
   // cache mView since it is used so much below...
    final View host = mView;
   	...
    int childWidthMeasureSpec = getRootMeasureS	pec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    // 执行测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	...
	//执行布局流程
	performLayout(lp, mWidth, mHeight);
	//执行绘制流程
	performDraw();
}

2.MeasureSpec

MeasureSpec表示的是一个32为的整型值,它的高2位表示测量模型SpecMode,低30位表示某种测量模式下的大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明应该如何测量这个View,其核心代码:

	 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        ///不执行测量模式
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        //精准测量模式
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        //最大值测量模式
        public static final int AT_MOST     = 2 << MODE_SHIFT;

       	//指定根据的大小和模型创建一个MeasureSpec
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    
        //从测量中获取测量模式
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        //从测量中获取测量大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
		//微调某个MeasureSpec的大小
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
    }

重点关注代码中的三种测量模式,这个在后面Measure阶段会用到。

  • UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。
  • EXACTLY: 精准测量模式,当是视图的layout_width或者layout_height指定为具体数据值或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值。
  • AT_MOST:最大值模式,当该视图的layout_width或者layout_height执行为warp_content是生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
    对DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定。对普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。

3.Measure测量

Measure操作用来计算View的实际大小,由之前分析源码可知,页面测量流程是从performMeasure方法开始的,核心代码:

	private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }

可以看到,具体的测量操作是分发给ViewGroup的,由ViewGroup在它的measureChild方发中传递给子View,代码如下。ViewGroup通过遍历自身所有的子View,并逐个调用子View的measure方法实现测量操作。

//遍历测量ViewGroup中所有的View
 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
                 //当View的可见行处于GONE状态时,不对其进行测量
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
 }
 //测量某个执行的View
  protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
		//根据容器的MeasureSpec和子View的LayoutParams等信息计算出子View的MeasureSpce
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

最终测量是用过回调onMeasure方法实现的,这个通常由View的特定子类自己实现,开发者也可以通过重写这方法实现自定义View。

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 	   ....
 	   onMeasure(widthMeasureSpec, heightMeasureSpec);
 	   ....
 }
//如果需要自定义测量过程,则子类可以重写这个方法
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//如果View没有重写onMeasure方法,则默认会直接调用getDefaultSize来获得View的宽高
 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
}

4.Layout布局

Layout过程用来确定View在父容器中的布局位置,它是由父容器获取子View的位置参数后,调用子View的layout方法并将位置参数传入实现的,ViewRootImpl的performLayout代码:

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
     ....
     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
     ....
}
//View.java
public void layout(int l, int t, int r, int b) {
	....
	onLayout(changed, l, t, r, b);
	....
}
//空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

5.Draw画布

Draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始,核心代码:

 private void performDraw() {
 		....
	 boolean canUseAsync = draw(fullRedrawNeeded);
 		....
 }
private boolean draw(boolean fullRedrawNeeded) {
	....
	  if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
      	}
	....
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
      ....
     mView.draw(canvas);
      ....
}

可以看到最终调用每个View的draw方法绘制每个具体的View,绘制基本上可以分为六个步骤:

public void draw(Canvas canvas) {
	....
	//步骤一:绘制View的背景
   drawBackground(canvas);
	
   ....
   //步骤二:如果需要,保存canvas的图层,为fading做准备
   saveCount = canvas.getSaveCount();
  ....
   if (drawTop) {
    topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
   }
   if (drawBottom) {
      bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
   }
   if (drawLeft) {
     leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
   }
   if (drawRight) {
     rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
   }
   ....
   //步骤三:绘制View 的内容
   onDraw(canvas);

   ....
   //步骤四:绘制View的子View
   dispatchDraw(canvas);

   ....
   //步骤五:如果需要,绘制View的fading边缘并恢复图层
   canvas.drawRect(left, top, right, top + length, p);
   ....
   canvas.restoreToCount(saveCount);

   ....
   //步骤六:绘制View的装饰(例如滚动条)
    onDrawScrollIndicators(canvas);
    onDrawScrollBars(canvas);
 }

猜你喜欢

转载自blog.csdn.net/weixin_44785058/article/details/107944630
今日推荐