Android View底层到底是怎么绘制的详解手机开发

Android绘制链图:

Android View底层到底是怎么绘制的详解手机开发

网上很多讲Android  view的绘制流程往往只讲到了Measure – Layout – Draw。

但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。

 public void setContentView(@LayoutRes int layoutResID) {         getWindow().setContentView(layoutResID);         initWindowDecorActionBar();     }

而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么

<span style="font-size: 16px;"> <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());     }

根据人家给我们的注释,这段代码是创建一个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);   
} 

调用然后系统再调用Measure – Layout – Draw实现了View的绘制。

我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:

Android 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

measure过程确定视图的大小,而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; 
    }

 函数中参数l、t、r、b是指view的左、上、右、底的位置,通过这几个参数来确定view在Windows的位置。

在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


draw过程调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,对于activity来说就是调用的PhoneWindow.DecorView。

*      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的源码。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5885.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论