安卓VIEW的绘制过程

来源:互联网 发布:php框架有哪些 编辑:程序博客网 时间:2024/05/18 18:21

 在View绘制之前我们首先看一下整个Android UI的结构,这是一张整体框架图:
a
 首先获取一个WindowManagerService即Wms,然后通过会话传递到ViewRoot,ViewRoot是一个继承Handler的类,该类负责View的处理逻辑,然后作用到DecoView,DecoView负责两部分的视图,即标题View以及内容View,我们在Activity常用的setContentView就是设置内容区域的视图。
 在启动一个Activity时,执行了OnCreate方法,然后执行了mContentParent.addView其实就是加载DecoView的ContentView,而加载的函数是通过ViewRoot中scheduleTraversals实现的,进而调用了ViewRoot的performTraversals函数。performTraversals是绘制ContentView的重要函数,而View的绘制主要涉及三个方法:onMeasure()、onLayout()和onDraw()。
(1).onMeasure主要用于计算视图的大小,onLayout主要用于确定view在ContentView中的位置,onDraw主要是对视图进行绘制。
(2).在执行onMeasure()、onLayout()方法时都会先通过相应的标志位或者对应的坐标点来判断是否需要执行对应的函数,如我们经常调用的invalidate方法就只会执行onDraw方法,因为此时的视图大小和位置均未发生改变,除非调用requestLayout方法完整强制进行view的绘制,从而执行上面三个方法。

1. measure函数分析

 首先我们接着看一下上面的图,整个Activity中View的绘制是从PhoneWindow到DecoView,而DecoView是继承自FrameLayout的,为一个ViewGroup类,该ViewGroup内部的ContentViews又是一个ViewGroup实例,依次内嵌View或ViewGroup形成一个View树
b
 故measure函数的作用是为整个View树计算实际的大小,即设置每个View对象的布局大小。
 measure方法接受两个参数,widthMeasureSpec和heightMeasureSpec,这两个参数用于确定View的宽度和高度。而这两个参数是由类MeasureSpec的makeMeasureSpec函数方法生成的一个32位整数,该整数的高两位表示模式(specMode),低30位则是具体的尺寸大小(specSize)。
这两个数由以下方法获取

            int specMode = MeasureSpec.getMode(measureSpec);              int specSize = MeasureSpec.getSize(measureSpec);

specMode有三种类型UNSPECIFIED, EXACTLY和AT_MOST:
1. EXACTLY
 表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
 表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
 表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
 在View类中measure过程主要涉及三个函数,函数原型分别为

     public final void measure(int widthMeasureSpec, int heightMeasureSpec)     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)     protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)

 这三个的调用关系如下:
Measure-> onMeasure-> setMeasuredDimension
 setMeasuredDimension为确定View的高宽的函数,而这个高宽值是通过onMeasure方法获取的,其onMeasure方法中关键函数为getDefaultSize来获取高宽值。
 第一个和第三个函数都是final类型的,不能重载,在ViewGroup派生的非抽象类中我们必须重载onMeasure函数。重载protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)发现该方法中有两个参数。通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。至于最外层的根视图,这两个值是由系统计算好的,performTraversals()方法可以发现如下代码

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  

 可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:

private int getRootMeasureSpec(int windowSize, int rootDimension) {      int measureSpec;      switch (rootDimension) {      case ViewGroup.LayoutParams.MATCH_PARENT:          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);          break;      case ViewGroup.LayoutParams.WRAP_CONTENT:          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);          break;      default:          measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);          break;      }      return measureSpec;  }  

 可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。
 View在measure过程中首先会调用measure(int widthMeasureSpec, int heightMeasureSpec)

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {     boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int oWidth  = insets.left + insets.right;            int oHeight = insets.top  + insets.bottom;            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);        }        // Suppress sign extension for the low bytes        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;        final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;        final boolean matchingSize = isExactly &&                getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&                getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);        if (forceLayout || !matchingSize &&                (widthMeasureSpec != mOldWidthMeasureSpec ||                        heightMeasureSpec != mOldHeightMeasureSpec)) {            // first clears the measured dimension flag            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;            resolveRtlPropertiesIfNeeded();            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);            if (cacheIndex < 0 || sIgnoreMeasureCache) {                // measure ourselves, this should set the measured dimension flag back                onMeasure(widthMeasureSpec, heightMeasureSpec);                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;            } else {                long value = mMeasureCache.valueAt(cacheIndex);                // Casting a long to int drops the high 32 bits, no mask needed                setMeasuredDimensionRaw((int) (value >> 32), (int) value);                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;            }            // flag not set, setMeasuredDimension() was not invoked, we raise            // an exception to warn the developer            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {                throw new IllegalStateException("onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension}

 measure()中onMeasure方法,onMeasure是可以重载的

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

 根据源码View的OnMeasure函数调用的getDefaultSize函数获知,默认情况下,控件都有一个最小尺寸,该值可以通过设置android:minHeight和android:minWidth来设置(无设置时缺省为0);在设置了背景的情况下,背景drawable的最小尺寸与前面设置的最小尺寸比较,两者取大者,作为控件的最小尺寸。在UNSPECIFIED情况下就选用这个最小尺寸,其它情况则根据允许尺寸来。
 onMeasure方法默认会调用setMeasuredDimension ()和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;  }  

 这里传入的measureSpec是一直从measure()方法中传递过来的。然后调用MeasureSpec.getMode()方法可以解析出specMode,调用MeasureSpec.getSize()方法可以解析出specSize。接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize,这也是系统默认的行为。之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

  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);}   private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }mMeasuredWidth和mMeasuredHeight即为当前View的宽高。

 当然,一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:

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];          if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {              measureChild(child, widthMeasureSpec, heightMeasureSpec);          }      }  }  

  这里首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下所示:

protected void measureChild(View child, int parentWidthMeasureSpec,          int parentHeightMeasureSpec) {      final LayoutParams lp = child.getLayoutParams();      final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,              mPaddingLeft + mPaddingRight, lp.width);      final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,              mPaddingTop + mPaddingBottom, lp.height);      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  }  

 可以看到,在第4行和第7行分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值,这个方法的内部细节就不再贴出。然后在第8行调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样了。
当然,onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:

public class MView extends View {      @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          setMeasuredDimension(100, 100);      }  }  

 这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是100*100。
 需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

2. layout函数分析

 layout过程的作用就是设定视图在父视图中的四个点(分别对应View四个成员变量mLeft,mTop,mLeft,mBottom)。同样layout也是被final修饰符限定为不能重载,不过在ViewGroup中onLayout函数被abstract修饰,即所有派生自ViewGroup的类必须实现onLayout函数,从而实现对其包含的所有子视图的布局设定。
 那么上述的measure结果与layout有什么关系,截取ViewRoot和FrameLayout两个类中onLayout函数的部分代码如下:
//ViewRoot的performTraversals函数measure之后对layout的调用代码

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

 layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。
 首先ViewRoot的performTraversals函数measure之后对layout的调用代码,然后layout方法调用setFrame方法来判断是否需要进行重绘,如果需要重绘就在setFrame方法中调用invalidate进行重绘,并调用onLayout方法,而onLayout方法为一个抽象方法,可以看到,ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
//FrameLayout的onLayout函数部分源码

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        final int count = getChildCount();        ……        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (child.getVisibility() != GONE) {                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                final int width = child.getMeasuredWidth();                final int height = child.getMeasuredHeight();                int childLeft = parentLeft;                int childTop = parentTop;                final int gravity = lp.gravity;                 if (gravity != -1) {                    final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;                     switch (horizontalGravity) {                        case Gravity.LEFT:                            childLeft = parentLeft + lp.leftMargin;                            break;                        case Gravity.CENTER_HORIZONTAL:                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin;                            break;                        case Gravity.RIGHT:                            childLeft = parentRight - width - lp.rightMargin;                            break;                        default:                            childLeft = parentLeft + lp.leftMargin;                    }                     switch (verticalGravity) {                        case Gravity.TOP:                            childTop = parentTop + lp.topMargin;                            break;                        case Gravity.CENTER_VERTICAL:                            childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin;                            break;                        case Gravity.BOTTOM:                            childTop = parentBottom - height - lp.bottomMargin;                            break;                        default:                            childTop = parentTop + lp.topMargin;                    }                }                 child.layout(childLeft, childTop, childLeft + width, childTop + height);            }        }    }

 从代码显然可知具体layout布局时,就是根据measure过程设置的高和宽,结合视图在父视图中的起始位置,再外加视图的layoutgravity属性来设置四个点的具体位置(在LinearLayout中还会增加对layoutweight属性的考虑)。这个过程相对没有measure那么复杂。
 需要注意的是在自定义组合控件的时候,我们可以根据需要不用或只用部分measure过程计算得到的尺寸,具体可以看下之前做的下拉刷新控件直接重载的onLayout函数:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    if (getChildCount() > 2) {        throw new IllegalStateException("NPullToFreshContainer can host only two direct child");    }            View headView = getChildAt(0);    View contentView = getChildAt(1);    if(headView != null){     headView.layout(0, -HEAD_VIEW_HEIGHT + mTatolScroll, getMeasuredWidth(), mTatolScroll);// mTatolScroll是下拉的位移值    }       if(contentView != null){    contentView.layout(0, mTatolScroll, getMeasuredWidth(), getMeasuredHeight());    }            if (mFirstLayout) {             HEAD_VIEW_HEIGHT = getChildAt(0).getMeasuredHeight();       mFirstLayout = false;    }}

 当然除ViewGroup外的其他view也可以重写onLayout,不过执行的是其他操作,如TextView中:

@Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        if (mDeferScroll >= 0) {            int curs = mDeferScroll;            mDeferScroll = -1;            bringPointIntoView(Math.min(curs, mText.length()));        }    }

 那么我们来看下layout()方法中的代码是什么样的吧,如下所示:

public void layout(int l, int t, int r, int b) {      …………..    int oldL = mLeft;      int oldT = mTop;      int oldB = mBottom;       int oldR = mRight;      boolean changed = setFrame(l, t, r, b);      if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {          if (ViewDebug.TRACE_HIERARCHY) {              ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);          }          onLayout(changed, l, t, r, b);          mPrivateFlags &= ~LAYOUT_REQUIRED;          if (mOnLayoutChangeListeners != null) {              ArrayList<OnLayoutChangeListener> listenersCopy =                      (ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT;  }  

 在layout()方法中,首先会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会在第11行调用onLayout()方法。进入onLayout()方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置,即onLayout只是当View是ViewGroup时,去调用子View的layout(),当然也可以理解为在onLayout中改变了默认的四个边界值而达到所说的设置子视图的显示位置。通过ViewGroup调用的layout方法,而layout方法又调用了setFrame,setFrame为确定视图在布局中所在位置函数(调用invalidate方法)。
代码如下:

protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  

 可以看到,ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
此外,说明一点:getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。
 首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

3. onDraw()函数分析

 measure和layout的过程都结束后,接下来就进入到draw的过程。在这里才真正地开始对视图进行绘制。ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步,其中第二步和第五步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。
1、调用background.draw(canvas)绘制该View的背景
3、调用onDraw(canvas)方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw(canvas)方法绘制子视图(ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,其内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法)
6、调用onDrawScrollBars(canvas)绘制滚动条
代码如下所示:

public void draw(Canvas canvas) {      if (ViewDebug.TRACE_HIERARCHY) {          ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);      }       final int privateFlags = mPrivateFlags;       final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&              (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);      mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;      // Step 1, draw the background, if needed      int saveCount;      if (!dirtyOpaque) {          final Drawable background = mBGDrawable;          if (background != null) {              final int scrollX = mScrollX;              final int scrollY = mScrollY;              if (mBackgroundSizeChanged) {                  background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);                  mBackgroundSizeChanged = false;              }              if ((scrollX | scrollY) == 0) {                  background.draw(canvas);              } else {                  canvas.translate(scrollX, scrollY);                  background.draw(canvas);                  canvas.translate(-scrollX, -scrollY);              }          }      }      final int viewFlags = mViewFlags;      boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;      boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;      if (!verticalEdges && !horizontalEdges) {          // Step 3, draw the content          if (!dirtyOpaque) onDraw(canvas);          // Step 4, draw the children          dispatchDraw(canvas);          // Step 6, draw decorations (scrollbars)          onDrawScrollBars(canvas);          // we're done...          return;      }  } 

 可以看到,第一步是从第9行代码开始的,这一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。
接下来的第三步是在第34行执行的,这一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。
 第三步完成之后紧接着会执行第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。

protected void dispatchDraw(Canvas canvas) {        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);        final int childrenCount = mChildrenCount;        final View[] children = mChildren;        int flags = mGroupFlags;        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;            final boolean buildCache = !isHardwareAccelerated();            for (int i = 0; i < childrenCount; i++) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {                    final LayoutParams params = child.getLayoutParams();                    attachLayoutAnimationParameters(child, params, i, childrenCount);                    bindLayoutAnimation(child);                    if (cache) {                        child.setDrawingCacheEnabled(true);                        if (buildCache) {                            child.buildDrawingCache(true);                        }                    }                }            }            final LayoutAnimationController controller = mLayoutAnimationController;            if (controller.willOverlap()) {                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;            }            controller.start();            mGroupFlags &= ~FLAG_RUN_ANIMATION;            mGroupFlags &= ~FLAG_ANIMATION_DONE;            if (cache) {                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;            }            if (mAnimationListener != null) {                mAnimationListener.onAnimationStart(controller.getAnimation());            }        }        int clipSaveCount = 0;        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;        if (clipToPadding) {            clipSaveCount = canvas.save();            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,                    mScrollX + mRight - mLeft - mPaddingRight,                    mScrollY + mBottom - mTop - mPaddingBottom);        }        // We will draw our child's animation, let's reset the flag        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;        boolean more = false;        final long drawingTime = getDrawingTime();        if (usingRenderNodeProperties) canvas.insertReorderBarrier();        // Only use the preordered list if not HW accelerated, since the HW pipeline will do the        // draw reordering internally        final ArrayList<View> preorderedList = usingRenderNodeProperties                ? null : buildOrderedChildList();        final boolean customOrder = preorderedList == null                && isChildrenDrawingOrderEnabled();        for (int i = 0; i < childrenCount; i++) {            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;            final View child = (preorderedList == null)                    ? children[childIndex] : preorderedList.get(childIndex);            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                more |= drawChild(canvas, child, drawingTime);            }        }        if (preorderedList != null) preorderedList.clear();        // Draw any disappearing views that have animations        if (mDisappearingChildren != null) {            final ArrayList<View> disappearingChildren = mDisappearingChildren;            final int disappearingCount = disappearingChildren.size() - 1;            // Go backwards -- we may delete as animations finish            for (int i = disappearingCount; i >= 0; i--) {                final View child = disappearingChildren.get(i);                more |= drawChild(canvas, child, drawingTime);            }        }        if (usingRenderNodeProperties) canvas.insertInorderBarrier();        if (debugDraw()) {            onDebugDraw(canvas);        }        if (clipToPadding) {            canvas.restoreToCount(clipSaveCount);        }        // mGroupFlags might have been updated by drawChild()        flags = mGroupFlags;        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {            invalidate(true);        }        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&                mLayoutAnimationController.isDone() && !more) {            // We want to erase the drawing cache and notify the listener after the            // next frame is drawn because one extra invalidate() is caused by            // drawChild() after the animation is over            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;            final Runnable end = new Runnable() {               public void run() {                   notifyAnimationListener();               }            };            post(end);        }    }

 以上都执行完后就会进入到第六步,也是最后一步,这一步的作用是对视图的滚动条进行绘制。当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。
通过以上流程分析,相信大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西。

0 0
原创粉丝点击