Android view 工作流程

来源:互联网 发布:网络调研方案 编辑:程序博客网 时间:2024/04/29 22:09

view基本认识

回想你第一次看到Android设备时,上面各种酷炫的应用和游戏是不是让多年使用塞班手机的你感到amazing?能看到这篇文章说明你现在的工作多少和android开发相关。或许你是跟着教程写了一个HelloWold运行在模拟器或者真机上,结果出来的瞬间会有些自豪。心想着自己也是会android应用开发的人了。

接着可能陆陆续续的写了几个小Demo,这时候对android应用的印象多了四大组件等一些概念,提到view,你可能会说,视图不就是窗口,而一个窗口往往就是一个activity。所以会有一个activity就是一个view的错觉。其实也不能说完全错了,这其中确实有玄机。

setContentView说起

为啥会让我们有activity即view的印象,原因就在于Activity一般都在onCreate函数中里使用setContentView设置UI界面。我们略作分析:

mWindow为何物?

Activity.java

  

[java] view plaincopy
  1.  /** 
  2.      * 根据布局文件来填充activity的view。 
  3.      */  
  4.     public void setContentView(int layoutResID) {  
  5.         getWindow().setContentView(layoutResID);//我们需要看下getWindow()返回的是什么。  
  6. }  
  7. /** 
  8.      * Retrieve the current {@link android.view.Window} for the activity. 
  9.      * This can be used to directly access parts of the Window API that 
  10.      * are not available through Activity/Screen. 
  11.      *  
  12.      * @return Window The current window, or null if the activity is not 
  13.      *         visual. 
  14.      */  
  15.     public Window getWindow() {  
  16.         return mWindow;// 是window的一个实例  
  17.     }  

mWindow是何方神圣,又是谁创造了它,我们control+F一下在attach函数中找到了它mWindow =PolicyManager.makeNewWindow(this),不过又多出来一个PolicyManager,这货又是干啥的,我们进去看看先。

[java] view plaincopy
  1. public final class PolicyManager {  
  2.     private static final String POLICY_IMPL_CLASS_NAME =  
  3.         "com.android.internal.policy.impl.Policy";  
  4.   
  5.     private static final IPolicy sPolicy;  
  6.   
  7.     static {  
  8.         // Pull in the actual implementation of the policy at run-time  
  9.         try {  
  10.             Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);  
  11.             sPolicy = (IPolicy)policyClass.newInstance();  
  12.         } catch (ClassNotFoundException ex) {  
  13.             throw new RuntimeException(  
  14.                     POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);  
  15. 。。。。。。  
  16. // The static methods to spawn new policy-specific objects  
  17.     public static Window makeNewWindow(Context context) {  
  18.         return sPolicy.makeNewWindow(context);  
  19.     }  
  20. }  


该类代码比较少,申明了一个Policy类型的单例对象,我们再看看它是神马。

[java] view plaincopy
  1. public PhoneWindow makeNewWindow(Context context) {  
  2.         return new PhoneWindow(context);//亲,注意啦,出现关键词PhoneWindow。  
  3. }  

到这里我们弄清楚了一个事实,mWindow就是一个PhoneWindow对象同样的手法,我们可以知道mWindowManager成员变量的真实类型是LocalWindowManager。

深入setContentView

弄清楚了以上概念,我们可以重新回到setContenView函数了。根据上面的结论,直接到PhoneWindow去找它。

  

[java] view plaincopy
  1. public void setContentView(View view, ViewGroup.LayoutParams params) {  
  2.        if (mContentParent == null) {  
  3.            installDecor();  
  4.        } else {  
  5.            mContentParent.removeAllViews();  
  6.        }  
  7.        mContentParent.addView(view, params);  
  8.        final Callback cb = getCallback();  
  9.        if (cb != null) {  
  10.            cb.onContentChanged();  
  11.        }  
  12.    }  

可以知道mContentParent是viewGroup类型,它存在时就负责把view加载出来,不存在的时候会走installDecor方法。

    

[java] view plaincopy
  1.  private void installDecor() {  
  2.         if (mDecor == null) {  
  3.             mDecor = generateDecor();//创建mDecor,它是DecorView类型,继承于FrameLayout。  
  4.             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
  5.             mDecor.setIsRootNamespace(true);  
  6.         }  
  7.         if (mContentParent == null) {  
  8.             mContentParent = generateLayout(mDecor);  
  9.             //创建标题栏  
  10.             mTitleView = (TextView)findViewById(com.android.internal.R.id.title);  
  11.           ......  
  12.         }  
  13. }  

这里需要我们进一步看下generateDecor()方法:

[java] view plaincopy
  1. protected ViewGroup generateLayout(DecorView decor) {  
  2.         // Apply data from current theme.  
  3. //1,根据getWindowStyle()返回的数组来设定一些窗口属性值feature,如是否全屏,是否带标题栏。  
  4.         TypedArray a = getWindowStyle();  
  5.         mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);  
  6.         int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)  
  7.                 & (~getForcedWindowFlags());  
  8.         if (mIsFloating) {  
  9.             setLayout(WRAP_CONTENT, WRAP_CONTENT);  
  10.             setFlags(0, flagsToUpdate);  
  11.         } else {  
  12.             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);  
  13.         }  
  14.   
  15.         if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {  
  16.             requestFeature(FEATURE_NO_TITLE);  
  17.         }  
  18.   
  19.         if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {  
  20.             setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));  
  21.         }  
  22.   
  23.         if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {  
  24.             setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));  
  25.         }  
  26.         ......  
  27.         // Inflate the window decor.  
  28. //2,根据上面设定的features值,决定加载何种窗口布局文件。  
  29.         int layoutResource;  
  30.         int features = getLocalFeatures();  
  31.         // System.out.println("Features: 0x" + Integer.toHexString(features));  
  32.         if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
  33.             if (mIsFloating) {  
  34.                 layoutResource = com.android.internal.R.layout.dialog_title_icons;  
  35.             } else {  
  36.                 layoutResource = com.android.internal.R.layout.screen_title_icons;  
  37.             }  
  38.         ......  
  39.         }  
  40.         mDecor.startChanging();  
  41. //3,把特定的view添加到decorView里。  
  42.         View in = mLayoutInflater.inflate(layoutResource, null);  
  43.         decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
  44. //4,这个contentParent由findViewById返回,实际上就是mDecorView一部分。  
  45.         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
  46.         ......  
  47.         mDecor.finishChanging();  
  48.         return contentParent;  
  49. }  

窗口布局文件解析

上面第二步提到根据上面设定的features值,决定加载何种窗口布局文件,我们找一个系统窗口布局文件分析一下:

路径在:.frameworks/base/core/res/layout/screen_title.xml 

[java] view plaincopy
  1. <!--  
  2. This is an optimized layout for a screen, with the minimum set of features  
  3. enabled.  
  4. -->  
  5.   
  6. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  7.     android:orientation="vertical"  
  8.     android:fitsSystemWindows="true">  
  9.     <FrameLayout  
  10.         android:layout_width="match_parent"   
  11.         android:layout_height="?android:attr/windowTitleSize"  
  12.         style="?android:attr/windowTitleBackgroundStyle">  
  13.         <TextView android:id="@android:id/title"   
  14.             style="?android:attr/windowTitleStyle"  
  15.             android:background="@null"  
  16.             android:fadingEdge="horizontal"  
  17.             android:gravity="center_vertical"  
  18.             android:layout_width="match_parent"  
  19.             android:layout_height="match_parent" />  
  20.     </FrameLayout>  
  21.     <FrameLayout android:id="@android:id/content"  
  22.         android:layout_width="match_parent"   
  23.         android:layout_height="0dip"  
  24.         android:layout_weight="1"  
  25.         android:foregroundGravity="fill_horizontal|top"  
  26.         android:foreground="?android:attr/windowContentOverlay" />  
  27. </LinearLayout>  

 可以看到是一个简单的布局文件,一个LinearLayout下包含了两个子FrameLayout视图,第一个FrameLayout用来显示标题栏(TitleBar);第二个FrameLayout用来显示id为content视图。

穿越回顾一下view结构


以上我们由setcontentView()引出了两个在view界举足轻重的大佬PhoneWindow和DecorView,它们之间的关系可以简单用下面的图来表示。

        

总结

举个不太恰当的类比:如果说PhoneWindow是画,一种抽象概念的画,那DecorView会更具体一点,它可以是山水画。而MyView就是具体的唐伯虎手下的凤凰傲意图了。

这里说的View、DecorView等都是UI单元,这些UI单元工作都在onDraw函数中完成。如果把onDraw想象成画图过程,那我们需要知道画布是什么?查阅资料后,得出答案就是Surface,关于它的前世今生还需看一下源码ViewRoot.java。

[java] view plaincopy
  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     View mView;  
  4. private final Surface mSurface = new Surface();  
  5.  }  

以上我们收集齐了PhoneWindow,DecorView,ViewRoot,Surface四颗神奇的龙珠,让我们一起把view召唤到手机屏幕上吧。

遍历View树performTraversals()执行过程

view树遍历概述

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

  

[java] view plaincopy
  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. }  

下面我们就来会会view那三部曲吧。

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

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


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

   

[java] view plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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 plaincopy
  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.     }  

整个view的工作流程那是相当复杂的,这需要静下心来,加以时日细细揣摩才能略有体会,本人写有小Demo一个,就当抛砖引玉:ClickMe!


原创粉丝点击