measure layout draw

框架分析

在之前的下拉刷新中,小结过触屏消息先到WindowManagerServiceWms)然后顺次传递给ViewRoot(派生自Handler),经decor viewActivity再传递给指定的View,这次整理View的绘制流程,通过源码可知,这个过程应该没有涉及到IPC(或者我没有发现),需要绘制时在UI线程中通过ViewRoot发送一个异步请求消息,然后ViewRoot自己接收并不处理这个消息。

在正式进入View绘制之前,首先需要明确一下Android UI的架构组成,偷图如下:

 

上述架构很清晰的呈现了ActivityWindowDecorView(及其组成)、ViewRootWMS之间的关系,我通过源码简单理了下从启动Activity到创建View的过程,大致如下

 

在上图中,performLaunchActivity函数是关键函数,除了新建被调用的Activity实例外,还负责确保Activity所在的应用程序启动、读取manifest中关于此activity设置的主题信息以及上图中对“6.onCreate”调用也是通过对mInstrumentation.callActivityOnCreate来实现的。图中的“8. mContentParent.addView”其实就是架构图中phoneWindowDecorView里面的ContentViews,该对象是一个ViewGroup类实例。在调用AddView之后,最终就会触发ViewRoot中的scheduleTraversals这个异步函数,从而进入ViewRootperformTraversals函数,在performTraversals函数中就启动了View的绘制流程。

performTraversals函数在2.3.5版本源码中就有近六百行的代码,跟我们绘制view相关的可以抽象成如下的简单流程图

 

流程图中的host其实就是mView,而ViewRoot中的这个mView其实就是DecorView,之所以这么说,又得具体看源码中ActivityThreadhandleResumeActivity函数,在这里我就不展开了。上述流程主要调用了Viewmeasurelayoutdraw三个函数。

performTraversals(),该函数就是android系统View树遍历工作的核心。一眼看去,发现这个函数挺长的,但是逻辑是非常清晰的,其执行过程可简单概括为根据之前所有设置好的状态,判断是否需要计算视图大小(measure)、是否需要重新安置视图的位置(layout),以及是否需要重绘(draw)视图,可以用以下图来表示该流程。

  

[java]  view plain copy
  1.   private void performTraversals() {  
  2.         // cache mView since it is used so much below...  
  3. //1 处理mAttachInfo的初始化,并根据resize、visibility改变的情况,给相应的变量赋值。  
  4.         final View host = mView;  
  5.         final View.AttachInfo attachInfo = mAttachInfo;  
  6.         final int viewVisibility = getHostVisibility();  
  7.         boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
  8.                 || mNewSurfaceNeeded;  
  9.         float appScale = mAttachInfo.mApplicationScale;  
  10.         WindowManager.LayoutParams params = null;  
  11.         if (mWindowAttributesChanged) {  
  12.             mWindowAttributesChanged = false;  
  13.             surfaceChanged = true;  
  14.             params = lp;  
  15.         }  
  16.         Rect frame = mWinFrame;  
  17.         if (mFirst) {  
  18.   
  19.             // For the very first time, tell the view hierarchy that it  
  20.             // is attached to the window.  Note that at this point the surface  
  21.             // object is not initialized to its backing store, but soon it  
  22.             // will be (assuming the window is visible).  
  23.             attachInfo.mSurface = mSurface;  
  24.             attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||  
  25.                     lp.format == PixelFormat.RGBX_8888;  
  26.             attachInfo.mHasWindowFocus = false;  
  27.             attachInfo.mWindowVisibility = viewVisibility;  
  28.             ......  
  29.         }   
  30. //2 如果mLayoutRequested判断为true,那么说明需要重新layout,不过在此之前那必须重新measure。  
  31.         if (mLayoutRequested) {  
  32.             // Execute enqueued actions on every layout in case a view that was detached  
  33.             // enqueued an action after being detached  
  34.             getRunQueue().executeActions(attachInfo.mHandler);  
  35.   
  36.             if (mFirst) {  
  37.                 ......  
  38.             }   
  39.         }  
  40. //3 判断是否有子视图的属性发生变化,ViewRoot需要获取这些变化。  
  41.         if (attachInfo.mRecomputeGlobalAttributes) {  
  42.             ......  
  43.         }  
  44.   
  45.         if (mFirst || attachInfo.mViewVisibilityChanged) {  
  46.             ......  
  47.         }  
  48.   
  49.       
  50. //4 根据上面得到的变量数值,确定我们的view需要多大尺寸才能装下。于是就得measure了,有viewgroup的weight属性还得再做些处理  
  51.                  // Ask host how big it wants to be  
  52.                 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  53.                 mLayoutRequested = true;  
  54.             }  
  55.         }  
  56. //5 measure完毕,接下来可以layout了。  
  57.         final boolean didLayout = mLayoutRequested;  
  58.         boolean triggerGlobalLayoutListener = didLayout  
  59.                 || attachInfo.mRecomputeGlobalAttributes;  
  60.         if (didLayout) {  
  61.             host.layout(00, host.mMeasuredWidth, host.mMeasuredHeight);  
  62.   
  63.         }  
  64.   
  65. //6 如果mFirst为true,那么会进行view获取焦点的动作。  
  66.         if (mFirst) {  
  67.             mRealFocusedView = mView.findFocus();  
  68.         }  
  69.   
  70.         boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();  
  71. //7 终于,来到最后一步,前面的工作可以说都是铺垫,都是为了draw而准备的。  
  72.         if (!cancelDraw && !newSurface) {  
  73.             mFullRedrawNeeded = false;  
  74.             draw(fullRedrawNeeded);  
  75. }  

measure过程分析

因为DecorView实际上是派生自FrameLayout的类,也即一个ViewGroup实例,该ViewGroup内部的ContentViews又是一个ViewGroup实例,依次内嵌ViewViewGroup形成一个View树。所以measure函数的作用是为整个View树计算实际的大小,设置每个View对象的布局大小(“窗口”大小)。实际对应属性就是View中的mMeasuredHeight(高)和mMeasureWidth(宽)。

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)

前面两个函数都是final类型的,不能重载,为此在ViewGroup派生的非抽象类中我们必须重载onMeasure函数,实现measure的原理是:假如View还有子View,则measureView,直到所有的子View完成measure操作之后,再measure自己。ViewGroup中提供的measureChildmeasureChildWithMargins就是实现这个功能的。

在具体介绍测量原理之前还是先了解些基础知识,即measure函数的参数由类measureSpecmakeMeasureSpec函数方法生成的一个32位整数,该整数的高两位表示模式(Mode),低30位则是具体的尺寸大小(specSize)。

MeasureSpec有三种模式分别是UNSPECIFIED, EXACTLYAT_MOST,各表示的意义如下

如果是AT_MOSTspecSize代表的是最大可获得的尺寸;

如果是EXACTLYspecSize代表的是精确的尺寸;

如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

那么对于一个View的上述ModespecSize值默认是怎么获取的呢,他们是根据ViewLayoutParams参数来获取的:

参数为fill_parent/match_parent时,ModeEXACTLYspecSize为剩余的所有空间;

参数为具体的数值,比如像素值(pxdp),ModeEXACTLYspecSize为传入的值;

参数为wrap_contentModeAT_MOSTspecSize运行时决定。

具体测量原理

上面提供的ModespecSize只是程序员对View的一个期望尺寸,最终一个View对象能从父视图得到多大的允许尺寸则由子视图期望尺寸和父视图能力尺寸(可提供的尺寸)两方面决定。关于期望尺寸的设定,可以通过在布局资源文件中定义的android:layout_widthandroid:layout_height来设定,也可以通过代码在addView函数调用时传入的LayoutParams参数来设定。父View的能力尺寸归根到最后就是DecorView尺寸,这个尺寸是全屏,由手机的分辨率决定。期望尺寸、能力尺寸和最终允许尺寸的关系,我们可以通过阅读measureChildmeasureChildWithMargins都会调用的getChildMeasureSpec函数的源码来获得,下面简单列表说明下三者的关系

父视图能力尺寸

子视图期望尺寸

子视图最终允许尺寸

EXACTLY + Size1

EXACTLY + Size2

EXACTLY + Size2

EXACTLY + Size1

fill_parent/match_parent

EXACTLY+Size1

EXACTLY + Size1

wrap_content

AT_MOST+Size1

AT_MOST+Size1

EXACTLY + Size2

EXACTLY+Size2

AT_MOST+Size1

fill_parent/match_parent

AT_MOST+Size1

AT_MOST+Size1

wrap_content

AT_MOST+Size1

UNSPECIFIED+Size1

EXACTLY + Size2

EXACTLY + Size2

UNSPECIFIED+Size1

fill_parent/match_parent

UNSPECIFIED+0

UNSPECIFIED+Size1

wrap_content

UNSPECIFIED+0

上述表格展现的是子视图最终允许得到的尺寸,显然147三项没有对Size1Size2进行比较,所以允许尺寸是可以大于父视图的能力尺寸的,这个时候最终的视图尺寸该是多少呢?AT_MOSTUNSPECIFIEDView又该如何决策最终的尺寸呢? 

通过Demo演示的得到的结果,假如Size2Size1的尺寸大,假如不使用滚动效果的话,子视图超出部分将被裁剪掉,该父视图中如果在该子视图后面还有其他视图,那么也将被裁剪掉,但是通过调用其getVisibility还是显示该控件是可见的,所以裁剪后控件依然是有的,只是用户没办法观察到;在使用滚动效果的情况下,就能将原本被裁剪掉的控件通过滚动显示出来。

对于第二个问题,根据源码ViewOnMeasure函数调用的getDefaultSize函数获知,默认情况下,控件都有一个最小尺寸,该值可以通过设置android:minHeightandroid:minWidth来设置(无设置时缺省为0);在设置了背景的情况下,背景drawable的最小尺寸与前面设置的最小尺寸比较,两者取大者,作为控件的最小尺寸。在UNSPECIFIED情况下就选用这个最小尺寸,其它情况则根据允许尺寸来。不过这个是默认规则,通过demo发现,TextViewAT_MOST+Size情况下,并不是以Size作为控件的最终尺寸,结果发现在TextView的源码中,重载了onMeasure函数,有价值的代码如下:

……

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

……

if (widthMode == MeasureSpec.AT_MOST) {

    width = Math.min(widthSize, width);

}

……

if (heightMode == MeasureSpec.AT_MOST) {

    height = Math.min(desired, heightSize);

}

……

至于其中的widthdesired值,感兴趣的同学可以具体关注下。虽然FrameWork提供了视图默认的尺寸计算规则,但是最终的视图布局大小可以重载onMeasure函数来修改计算规则,当然也可以不计算直接通过setMeasuredDimension来设置(需要注意的是,如果通过setMeasuredDimension的同时还要调用父类的onMeasure函数,那么在调用父类函数之前调用的setMeasuredDimension会无效果)。


计算视图大小(measure)的过程

整个view视图的Measure过程就是一个量体裁衣,按需分配的过程。看一下以下的递归过程:


从上图可以看出,measure过程始于ViewRoot的host.measure(),调的就是view类的measure()函数,该函数然后回调onMeasure。如果host对象是一个ViewGroup实例,一般会重载onMeasure,如果没有的话,则会执行view类中默认的onMeasure。合理的情况是编程人员重载onMeasure并逐一对里面的子view进行measure。我们可以看一下view的measure方法:

   

[java]  view plain copy
  1. /** 
  2.    * 该方法在需要确定view所需尺寸大小时调用,父视图会提供宽和高的属性约束。 
  3.    * 具体视图完全可以在onMeasure中改变这些。 
  4.    * @see #onMeasure(int, int) 
  5.    */  
  6.   public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  7.       if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  8.               widthMeasureSpec != mOldWidthMeasureSpec ||  
  9.               heightMeasureSpec != mOldHeightMeasureSpec) {  
  10.   
  11.           // 首先清除dimension的设值  
  12.           mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  13.           // measure 自己, 并设置dimension  
  14.           onMeasure(widthMeasureSpec, heightMeasureSpec);  
  15.           // 抛出未设值flag的异常  
  16.           if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  17.               throw new IllegalStateException("onMeasure() did not set the"  
  18.                       + " measured dimension by calling"  
  19.                       + " setMeasuredDimension()");  
  20.           }  
  21.           mPrivateFlags |= LAYOUT_REQUIRED;  
  22.       }  
  23.       mOldWidthMeasureSpec = widthMeasureSpec;  
  24.       mOldHeightMeasureSpec = heightMeasureSpec;  
  25.   }  

这里强烈建议去看看viewGroup实例FrameLayout和LinearLayout的onMeasure方法,一定会有所感悟的,尤其是LinerLayout的。这样对于viewGroup的专用标签pading和weight也会有新的体会。

[java]  view plain copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="100dip"  
  5.     >  
  6. <TextView    
  7.     android:layout_width="fill_parent"   
  8.     android:layout_height="20dip"   
  9.     android:layout_weight="2"  
  10.     android:text="@string/hello"  
  11.     />  
  12.     <ListView  
  13.     android:layout_width="fill_parent"  
  14.     android:layout_height="fill_parent"  
  15.     android:background="#ff00ff00"  
  16.     ></ListView>  
  17. </LinearLayout>  
  18.   
  19. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  20.     android:orientation="vertical"  
  21.     android:layout_width="fill_parent"  
  22.     android:layout_height="100dip"  
  23.     >  
  24. <TextView    
  25.     android:layout_width="fill_parent"   
  26.     android:layout_height="60dip"   
  27.     android:layout_weight="2"  
  28.     android:text="@string/hello"  
  29.     />  
  30.     <ListView  
  31.     android:layout_width="fill_parent"  
  32.     android:layout_height="0dip"  
  33.     android:layout_weight="2"  
  34.     android:background="#ff00ff00"  
  35.     ></ListView>  
  36. </LinearLayout>  

请问以上两布局有无不同,能否自行画出?

 

布局(layout)过程

执行完measure过程,也就是说各个view的大小尺寸已经登记在案,现在它们要确定的是自己应该置身于何处,也就是摆放在哪里。好吧,这个就是layout的职责所在,让父视图按照子视图的大小及布局参数,将子视图放置在合适的位置上。

同样需要看下以下流程图:


[java]  view plain copy
  1.  public void layout(int l, int t, int r, int b) {  
  2.         int oldL = mLeft;  
  3.         int oldT = mTop;  
  4.         int oldB = mBottom;  
  5.         int oldR = mRight;  
  6. //调用setFrame()函数给当前视图设置参数中指定的位置  
  7.         boolean changed = setFrame(l, t, r, b);  
  8.         if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  9.             if (ViewDebug.TRACE_HIERARCHY) {  
  10.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  11.             }  
  12. //回调onLayout()函数  
  13.             onLayout(changed, l, t, r, b);  
  14.             mPrivateFlags &= ~LAYOUT_REQUIRED;  
  15. //4.0新增监听可捕获layout变化  
  16.             if (mOnLayoutChangeListeners != null) {  
  17.                 ArrayList<OnLayoutChangeListener> listenersCopy =  
  18.                         (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();  
  19.                 int numListeners = listenersCopy.size();  
  20.                 for (int i = 0; i < numListeners; ++i) {  
  21.                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  22.                 }  
  23.             }  
  24.         }  
  25. //layout完成,清楚标签  
  26.         mPrivateFlags &= ~FORCE_LAYOUT;  
  27.     }  

       view中的该layout函数流程大概如下:

1,调用setFrame()将位置参数保存,这些参数会保存到view内部变量(mLeft,mTop,mRight,mButtom)。如果有数值的变化那么会调用invalidate()重绘那些区域。

2,回调onLayout(),View中定义的onLayout()函数默认什么都不做,View系统提供onLayout函数的目的是为了使系统包含有子视图的父视图能够在onLayout()函数对子视图进行位置分配,正因为这样,如果是viewGroup类型,就必须重载onLayout(),由此ViewGroup的onLayout为abstract也就很好解释了。

3,清楚mPrivateFlags中的LAYOUT_REQUIRED标记,因为layout的操作已经完成了。

为了对layout过程有更深的体会,有必要针对特定的一种viewGroup进行分析,我们还是把魔爪伸向linearLayout,看看它的onMeasure,onMeasure中会根据mOrientation变量,进行不同的layout,我们看一种就行了:

   

[java]  view plain copy
  1.  void layoutVertical() {  
  2.         final int paddingLeft = mPaddingLeft;  
  3.         int childTop;  
  4.         int childLeft;         
  5.         // 获得子视图可以用的宽度,顺便也把子视图左边沿的位置计算出来。  
  6.         final int width = mRight - mLeft;  
  7.         int childRight = width - mPaddingRight;       
  8.         // Space available for child  
  9.         int childSpace = width - paddingLeft - mPaddingRight;  
  10.           
  11.         final int count = getVirtualChildCount();  
  12. //根据父视图中的gravity属性,决定子视图的起始位置。  
  13.         final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;  
  14.         final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;  
  15.         switch (majorGravity) {  
  16.            case Gravity.BOTTOM:  
  17.                // mTotalLength contains the padding already  
  18.                childTop = mPaddingTop + mBottom - mTop - mTotalLength;  
  19.                break;  
  20.                // mTotalLength contains the padding already  
  21.            case Gravity.CENTER_VERTICAL:  
  22.                childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;  
  23.                break;  
  24.            case Gravity.TOP:  
  25.            default:  
  26.                childTop = mPaddingTop;  
  27.                break;  
  28.         }  
  29. //遍历所有子视图,为它们分配位置.setChildFrame()结果还是会调用child.layout()为子视图设置布局位置.  
  30.         for (int i = 0; i < count; i++) {  
  31.             final View child = getVirtualChildAt(i);  
  32.             if (child == null) {  
  33.                 childTop += measureNullChild(i);  
  34.     ......  
  35.                 childTop += lp.topMargin;  
  36.                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),  
  37.                         childWidth, childHeight);  
  38.                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);  
  39.                 i += getChildrenSkipCount(child, i);  
  40.             }  
  41.         }  
  42.     }  

绘制(draw)过程

这是见证奇迹的时刻,draw过程就是要把view对象绘制到屏幕上,如果它是一个viewGroup,则需要递归绘制它所有的子视图。视图中有哪些元素是需要绘制的呢?

1,view背景。所有view都会有一个背景,可以是一个颜色值,也可以是一张背景图片

2,视图本身内容。比如TextView的文字

3,渐变边框。就是一个shader对象,让视图看起来更具有层次感

4,滚动条。

按照惯例,还是先看一下绘制的总体流程吧:


眼尖的同学应该发现了,上面这张图比前面的measure和layout多了一步:draw()。在performTraversals()函数中调用的是viewRoot的draw()函数,在该函数中进行一系列的前端处理后,再调用host.draw()。

一般情况下,View对象不应该重载draw()函数,因此,host.draw()就是view.draw()。该函数内部过程也就是View系统绘制过程的核心过程,该函数中会依次绘制前面所说哦四种元素,其中绘制视图本身的具体实现就是回调onDraw()函数,应用程序一般也会重载onDraw()函数以绘制所设计的View的真正界面内容。

Google源码的注释太nice了,我加任何说辞都显得多余,就不画蛇添足了:

[java]  view plain copy
  1. public void draw(Canvas canvas) {  
  2.         if (ViewDebug.TRACE_HIERARCHY) {  
  3.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
  4.         }  
  5.   
  6.         final int privateFlags = mPrivateFlags;  
  7.         final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
  8.                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
  9.         mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
  10.   
  11.         /* 
  12.          * Draw traversal performs several drawing steps which must be executed 
  13.          * in the appropriate order: 
  14.          * 
  15.          *      1. Draw the background 
  16.          *      2. If necessary, save the canvas' layers to prepare for fading 
  17.          *      3. Draw view's content 
  18.          *      4. Draw children 
  19.          *      5. If necessary, draw the fading edges and restore layers 
  20.          *      6. Draw decorations (scrollbars for instance) 
  21.          */  
  22.   
  23.         // Step 1, draw the background, if needed  
  24.         int saveCount;  
  25.   
  26.         if (!dirtyOpaque) {  
  27.             final Drawable background = mBGDrawable;  
  28.             if (background != null) {  
  29.                 final int scrollX = mScrollX;  
  30.                 final int scrollY = mScrollY;  
  31.   
  32.                 if (mBackgroundSizeChanged) {  
  33.                     background.setBounds(00,  mRight - mLeft, mBottom - mTop);  
  34.                     mBackgroundSizeChanged = false;  
  35.                 }  
  36.   
  37.                 if ((scrollX | scrollY) == 0) {  
  38.                     background.draw(canvas);  
  39.                 } else {  
  40.                     canvas.translate(scrollX, scrollY);  
  41.                     background.draw(canvas);  
  42.                     canvas.translate(-scrollX, -scrollY);  
  43.                 }  
  44.             }  
  45.         }  
  46.   
  47.         // skip step 2 & 5 if possible (common case)  
  48.         final int viewFlags = mViewFlags;  
  49.         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
  50.         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
  51.         if (!verticalEdges && !horizontalEdges) {  
  52.             // Step 3, draw the content  
  53.             if (!dirtyOpaque) onDraw(canvas);  
  54.   
  55.             // Step 4, draw the children  
  56.             dispatchDraw(canvas);  
  57.   
  58.             // Step 6, draw decorations (scrollbars)  
  59.             onDrawScrollBars(canvas);  
  60.   
  61.             // we're done...  
  62.             return;  
  63.         }  
  64.   
  65.         /* 
  66.          * Here we do the full fledged routine... 
  67.          * (this is an uncommon case where speed matters less, 
  68.          * this is why we repeat some of the tests that have been 
  69.          * done above) 
  70.          */  
  71.   
  72.         boolean drawTop = false;  
  73.         boolean drawBottom = false;  
  74.         boolean drawLeft = false;  
  75.         boolean drawRight = false;  
  76.   
  77.         float topFadeStrength = 0.0f;  
  78.         float bottomFadeStrength = 0.0f;  
  79.         float leftFadeStrength = 0.0f;  
  80.         float rightFadeStrength = 0.0f;  
  81.   
  82.         // Step 2, save the canvas' layers  
  83.         int paddingLeft = mPaddingLeft;  
  84.   
  85.         final boolean offsetRequired = isPaddingOffsetRequired();  
  86.         if (offsetRequired) {  
  87.             paddingLeft += getLeftPaddingOffset();  
  88.         }  
  89.   
  90.         int left = mScrollX + paddingLeft;  
  91.         int right = left + mRight - mLeft - mPaddingRight - paddingLeft;  
  92.         int top = mScrollY + getFadeTop(offsetRequired);  
  93.         int bottom = top + getFadeHeight(offsetRequired);  
  94.   
  95.         if (offsetRequired) {  
  96.             right += getRightPaddingOffset();  
  97.             bottom += getBottomPaddingOffset();  
  98.         }  
  99.   
  100.         final ScrollabilityCache scrollabilityCache = mScrollCache;  
  101.         final float fadeHeight = scrollabilityCache.fadingEdgeLength;          
  102.         int length = (int) fadeHeight;  
  103.   
  104.         // clip the fade length if top and bottom fades overlap  
  105.         // overlapping fades produce odd-looking artifacts  
  106.         if (verticalEdges && (top + length > bottom - length)) {  
  107.             length = (bottom - top) / 2;  
  108.         }  
  109.   
  110.         // also clip horizontal fades if necessary  
  111.         if (horizontalEdges && (left + length > right - length)) {  
  112.             length = (right - left) / 2;  
  113.         }  
  114.   
  115.         if (verticalEdges) {  
  116.             topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));  
  117.             drawTop = topFadeStrength * fadeHeight > 1.0f;  
  118.             bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));  
  119.             drawBottom = bottomFadeStrength * fadeHeight > 1.0f;  
  120.         }  
  121.   
  122.         saveCount = canvas.getSaveCount();  
  123.   
  124.         int solidColor = getSolidColor();  
  125.         if (solidColor == 0) {  
  126.             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
  127.   
  128.             if (drawTop) {  
  129.                 canvas.saveLayer(left, top, right, top + length, null, flags);  
  130.             }  
  131.   
  132.             if (drawBottom) {  
  133.                 canvas.saveLayer(left, bottom - length, right, bottom, null, flags);  
  134.             }  
  135.         } else {  
  136.             scrollabilityCache.setFadeColor(solidColor);  
  137.         }  
  138.   
  139.         // Step 3, draw the content  
  140.         if (!dirtyOpaque) onDraw(canvas);  
  141.   
  142.         // Step 4, draw the children  
  143.         dispatchDraw(canvas);  
  144.   
  145.         // Step 5, draw the fade effect and restore layers  
  146.         final Paint p = scrollabilityCache.paint;  
  147.         final Matrix matrix = scrollabilityCache.matrix;  
  148.         final Shader fade = scrollabilityCache.shader;  
  149.   
  150.         if (drawTop) {  
  151.             matrix.setScale(1, fadeHeight * topFadeStrength);  
  152.             matrix.postTranslate(left, top);  
  153.             fade.setLocalMatrix(matrix);  
  154.             canvas.drawRect(left, top, right, top + length, p);  
  155.         }  
  156. 。。。。。  
  157.         canvas.restoreToCount(saveCount);  
  158.         // Step 6, draw decorations (scrollbars)  
  159.         onDrawScrollBars(canvas);  
  160.     }  

绘制完界面内容后,如果该视图还包含子视图,则调用dispatchDraw()函数,实际上起作用的还是viewGroup的dispatchDraw()函数。需要说明的是应用程序不应该再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。

[java]  view plain copy
  1.  protected void dispatchDraw(Canvas canvas) {  
  2.         final int count = mChildrenCount;  
  3.         final View[] children = mChildren;  
  4.         int flags = mGroupFlags;  
  5. //1 判断mGroupFlags是否设有FLAG_RUN_ANIMATION标识并且不为0.该layout动画指的是加载或移除子视图时候呈现的动画.  
  6.         if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {  
  7.             final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;  
  8.             final boolean buildCache = !isHardwareAccelerated();//硬件加速,4.0加入.  
  9.             for (int i = 0; i < count; i++) {  
  10.                 final View child = children[i];  
  11.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {  
  12.                     final LayoutParams params = child.getLayoutParams();  
  13.                     attachLayoutAnimationParameters(child, params, i, count);  
  14.                     bindLayoutAnimation(child);  
  15.             }  
  16.   
  17.         }  
  18. //2 处理padding属性,如果该viewGroup有设置.  
  19.         int saveCount = 0;  
  20.         final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;  
  21.         if (clipToPadding) {  
  22.             saveCount = canvas.save();  
  23.             canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,  
  24.                     mScrollX + mRight - mLeft - mPaddingRight,  
  25.                     mScrollY + mBottom - mTop - mPaddingBottom);  
  26.   
  27.         }  
  28. //3 开始绘制子视图动画之前先清除flag.  
  29.         // We will draw our child's animation, let's reset the flag  
  30.         mPrivateFlags &= ~DRAW_ANIMATION;  
  31.         mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;  
  32.   
  33.         boolean more = false;  
  34.         final long drawingTime = getDrawingTime();  
  35. //4 使用佛如循环,使viewGroup的子视图逐个调用drawChild函数.  
  36.         if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  37.             for (int i = 0; i < count; i++) {  
  38.                 final View child = children[i];  
  39.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  40.                     more |= drawChild(canvas, child, drawingTime);  
  41.                 }  
  42.             }  
  43.         } else {  
  44.             for (int i = 0; i < count; i++) {  
  45.                 final View child = children[getChildDrawingOrder(count, i)];  
  46.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  47.                     more |= drawChild(canvas, child, drawingTime);  
  48.                 }  
  49.             }  
  50.         }  
  51.   
  52. //5 Draw any disappearing views that have animations  
  53.         if (mDisappearingChildren != null) {  
  54.             final ArrayList<View> disappearingChildren = mDisappearingChildren;  
  55.             final int disappearingCount = disappearingChildren.size() - 1;  
  56.             // Go backwards -- we may delete as animations finish  
  57.             for (int i = disappearingCount; i >= 0; i--) {  
  58.                 final View child = disappearingChildren.get(i);  
  59.                 more |= drawChild(canvas, child, drawingTime);  
  60.             }  
  61.         }  
  62.   
  63.         if (clipToPadding) {  
  64.             canvas.restoreToCount(saveCount);  
  65.         }  
  66.   
  67. // mGroupFlags might have been updated by drawChild()  
  68.         flags = mGroupFlags;  
  69.   
  70.         if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {  
  71.             invalidate(true);  
  72.         }  
  73.     }  

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值