Android View底层到底是怎么绘制的
Android绘制链图:
网上很多讲Android view的绘制流程往往只讲到了Measure - Layout - Draw。
但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。
- public void setContentView(@LayoutRes int layoutResID) {
 - getWindow().setContentView(layoutResID);
 - initWindowDecorActionBar();
 - }
 
而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么
- <span style="font-size: 16px;"> </span><span style="font-size:14px;"> private void initWindowDecorActionBar() {
 - Window window = getWindow();
 - // Initializing the window decor can change window feature flags.
 - // Make sure that we have the correct set before performing the test below.
 - window.getDecorView();
 - if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
 - return;
 - }
 - mActionBar = new WindowDecorActionBar(this);
 - mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
 - mWindow.setDefaultIcon(mActivityInfo.getIconResource());
 - mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
 - }</span>
 
根据人家给我们的注释,这段代码是创建一个actionbar,初始化这个view和actionbar。这里面有一段很重要的代码:
- window.getDecorView();
 
正式这段代码告知系统可以从view的根节点开始绘制了,通过DecorView方法,decorview调用了performTraversals方法,我们来看下performTraversals源码:
- <span style="font-size:14px;">private void performTraversals() {
 - final View host = mView;
 - ...
 - host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 - ...
 - host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 - ...
 - draw(fullRedrawNeeded);
 - } </span>
 
调用然后系统再调用Measure - Layout - Draw实现了View的绘制。
我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:
到这里,系统会调用我们之前的比较熟悉的几个方法:Measure - Layout - Draw
Measure
Measure过程是计算视图大小,View中视图measure过程相关的方法主要有三个
- public final void measure(int widthMeasureSpec, int heightMeasureSpec)
 - protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
 - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 
measure调用onMeasure,onMeasure测量完成后setMeasureDimension,setMeasureDimension是final类型,view的子类不需要重写。
measure 源码:
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 - if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
 - widthMeasureSpec != mOldWidthMeasureSpec ||
 - heightMeasureSpec != mOldHeightMeasureSpec) {
 - // first clears the measured dimension flag
 - mPrivateFlags &= ~MEASURED_DIMENSION_SET;
 - if (ViewDebug.TRACE_HIERARCHY) {
 - ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
 - }
 - // measure ourselves, this should set the measured dimension flag back
 - onMeasure(widthMeasureSpec, heightMeasureSpec);
 - // flag not set, setMeasuredDimension() was not invoked, we raise
 - // an exception to warn the developer
 - if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
 - throw new IllegalStateException("onMeasure() did not set the"
 - + " measured dimension by calling"
 - + " setMeasuredDimension()");
 - }
 - mPrivateFlags |= LAYOUT_REQUIRED;
 - }
 - mOldWidthMeasureSpec = widthMeasureSpec;
 - mOldHeightMeasureSpec = heightMeasureSpec;
 - }
 
我们看一下OnMearsure方法:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 - setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
 - getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 - }
 
这个方法主要是实现setMeasuredDimension,这个方法是测量view的大小:
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
 - boolean optical = isLayoutModeOptical(this);
 - if (optical != isLayoutModeOptical(mParent)) {
 - Insets insets = getOpticalInsets();
 - int opticalWidth = insets.left + insets.right;
 - int opticalHeight = insets.top + insets.bottom;
 - measuredWidth += optical ? opticalWidth : -opticalWidth;
 - measuredHeight += optical ? opticalHeight : -opticalHeight;
 - }
 - setMeasuredDimensionRaw(measuredWidth, measuredHeight);
 - }
 
而对于这个measuredWidth和measuredHeight参数,系统却调了一个getDefaultSize();
- 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;
 - }
 
widthMeasureSpec和heightMeasureSpec决定了Mode和Size的值,widthMeasureSpec和heightMeasureSpec来自父视图,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了.
关于视图的measure过程可以阅读以下LinearLayout源码。
Layout
- public void layout(int l, int t, int r, int b) {
 - if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
 - onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
 - mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
 - }
 - int oldL = mLeft;
 - int oldT = mTop;
 - int oldB = mBottom;
 - int oldR = mRight;
 - boolean changed = isLayoutModeOptical(mParent) ?
 - setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 - if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
 - onLayout(changed, l, t, r, b);
 - mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
 - ListenerInfo li = mListenerInfo;
 - if (li != null && li.mOnLayoutChangeListeners != null) {
 - ArrayList<OnLayoutChangeListener> listenersCopy =
 - (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
 - int numListeners = listenersCopy.size();
 - for (int i = 0; i < numListeners; ++i) {
 - listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
 - }
 - }
 - }
 - mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
 - mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
 - }
 
在layout函数中,重载了一个空函数
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 - }
 
这个需要子类去实现的。
比如Linearlayout:
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
 - if (mOrientation == VERTICAL) {
 - layoutVertical();
 - } else {
 - layoutHorizontal();
 - }
 - }
 
具体实现请自行看源码。
而在最后无论是layoutVertical还是layoutHorizontal都会掉一个setChildFrame方法来控制显示位置。
- private void setChildFrame(View child, int left, int top, int width, int height) {
 - child.layout(left, top, left + width, top + height);
 - }
 
从上面看出,layout也是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。
Draw
* 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance)
根据view源码的注释,
1,绘制背景
2,保存画布图层
3,调用了onDraw方法,子类中实现onDraw方法
4,使用的dispatchDraw方法
View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。
有兴趣的可以看看onDraw的源码。
                    
                
                
            
        
浙公网安备 33010602011771号