Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析(上)

简介:

   在前面一篇文章中,我们分析了Android应用程序窗口的绘图表面的创建过程。Android应用程序窗口的绘图表面在创建完成之后,我们就可以从上到下地绘制它里面的各个视图了,即各个UI元素了。不过在绘制这些UI元素之前,我们还需要从上到下地测量它们实际所需要的大小,以及对它们的位置进行合适的安排,即对它们进行合适的布局。在本文中,我们就将详细地分析Android应用程序窗口的测量、布局以及绘制过程。

       从前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划这一系列的文章可以知道,Android应用程序窗口请求SurfaceFlinger服务创建了一个绘图表面之后,就可以接着请求为该绘图表面创建图形缓冲区,而当Android应用程序窗口往这些图形缓冲区填充好UI数据之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去,这样我们就可以看到应用程序窗口的UI了。

       Android应用程序窗口一般不会直接去操作分配给它的图形缓冲区,而是通过一些图形库API来操作。例如,在前面Android系统的开机画面显示过程分析一文中,使用C++来开发的开机动画应用程序bootanimation,它是通过OpenGL提供的API来绘制UI的。对于使用Java来开发的Android应用程序来说,它们一般是使用Skia图形库提供的API来绘制UI的。在Skia图库中,所有的UI都是绘制在画布(Canvas)上的,因此,Android应用程序窗口需要将它的图形缓冲区封装在一块画布里面,然后才可以使用Skia库提供的API来绘制UI。

       我们知道,一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前,我们首先要确定它里面的各个子UI元素在父UI元素里面的大小以及位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段,如图1所示:


图1 Android应用程序窗口渲染三步曲

       从前面Android应用程序窗口(Activity)的视图对象(View)的创建过程分析一文可以知道,Android应用程序窗口的顶层视图是一个类型为DecorView的UI元素,而从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文的Step 3又可以知道,这个顶层视图最终是由ViewRoot类的成员函数performTraversals来启动测量、布局和绘制操作的,这三个操作分别由DecorView类的成员函数measure和layout以及ViewRoot类的成员函数draw来实现的。

       接下来,我们就分别从DecorView类的成员函数measure和layout以及ViewRoot类的成员函数draw开始,分析Android应用程序窗口的测量、布局和绘制过程。

       1. Android应用程序窗口的测量过程

       DecorView类的成员函数measure是从父类View继承下来的,因此,我们就从View类的成员函数measure开始分析应用程序窗口的测量过程,如图2所示:


图2 Android应用程序窗口的测量过程

       这个过程可以分为3个步骤,接下来我们就详细分析每一个步骤。

       Step 1. View.measure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     int  mPrivateFlags;
     ......
     int  mOldWidthMeasureSpec = Integer.MIN_VALUE;
     ......
     int  mOldHeightMeasureSpec = Integer.MIN_VALUE;
     ......
     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;
             ......
             // 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;
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/View.java中。


       参数widthMeasureSpec和heightMeasureSpec用来描述当前正在处理的视图可以获得的最大宽度和高度。对于应用程序窗口的顶层视图来说,我们也可以认为这两个参数是用来描述应用程序窗口的宽度和高度。

       ViewRoot类的成员变量mPrivateFlags的类型为int,如果它的某一个位的值不等于0,那么就隐含着当前视图有一个相应的操作在等待执行中。ViewRoot类的另外两个成员变量mOldWidthMeasureSpec和mOldHeightMeasureSpec用来保存当前视图上一次可以获得的最大宽度和高度。

       当ViewRoot类的成员变量mPrivateFlags的FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作,这时候函数就需要重新测量当前视图的宽度和高度。此外,当参数widthMeasureSpec和heightMeasureSpec的值不等于ViewRoot类的成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec的值时,就表示当前视图上一次可以获得的最大宽度和高度已经失效了,这时候函数也需要重新测量当前视图的宽度和高度。

       当View类的成员函数measure决定要重新测量当前视图的宽度和高度之后,它就会首先将成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0,接着再调用另外一个成员函数onMeasure来真正执行测量宽度和高度的操作。View类的成员函数onMeasure执行完成之后,需要再调用另外一个成员函数setMeasuredDimension来将测量好的宽度和高度设置到View类的成员变量mMeasuredWidth和mMeasuredHeight中,并且将成员变量mPrivateFlags的EASURED_DIMENSION_SET位设置为1。这个操作是强制的,因为当前视图最终就是通过View类的成员变量mMeasuredWidth和mMeasuredHeight来获得它的宽度和高度的。为了保证这个操作是强制的,View类的成员函数measure再接下来就会检查成员变量mPrivateFlags的EASURED_DIMENSION_SET位是否被设置为1了。如果不是的话,那么就会抛出一个类型为IllegalStateException的异常来。

       View类的成员函数measure最后就会把参数widthMeasureSpec和heightMeasureSpec的值保存在成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec中,以便可以记录当前视图上一次可以获得的最大宽度和高度。

       View类的成员函数onMeasure一般是由其子类来重写的。例如,对于用来应用程序窗口的顶层视图的DecorView类来说,它是通过父类FrameLayout来重写祖父类View的成员函数onMeasure的。因此,接下来我们就分析FrameLayout类的成员函数onMeasure的实现。

       Step 2. rameLayout.onMeasure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public  class  FrameLayout  extends  ViewGroup {
     ......
     @Override
     protected  void  onMeasure( int  widthMeasureSpec,  int  heightMeasureSpec) {
         final  int  count = getChildCount();
         int  maxHeight =  0 ;
         int  maxWidth =  0 ;
         // Find rightmost and bottommost child
         for  ( int  i =  0 ; i < count; i++) {
             final  View child = getChildAt(i);
             if  (mMeasureAllChildren || child.getVisibility() != GONE) {
                 measureChildWithMargins(child, widthMeasureSpec,  0 , heightMeasureSpec,  0 );
                 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
             }
         }
         // Account for padding too
         maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
         maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;
         // Check against our minimum height and width
         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
         // Check against our foreground\\\'s minimum height and width
         final  Drawable drawable = getForeground();
         if  (drawable !=  null ) {
             maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
             maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
         }
         setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
                 resolveSize(maxHeight, heightMeasureSpec));
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/widget/FrameLayout.java中。


       FrameLayout类是从ViewGroup类继承下来的,后者用来描述一个视图容器,它有一个类型为View的数组mChildren,里面保存的就是它的各个子视图。ViewGroup类所供了两个成员函数getChildCount和getChildAt,它们分别用来获得一个视图容器所包含的子视图的个数,以及获得每一个子视图。

       FrameLayout类的成员函数onMeasure首先是调用另一个成员函数measureChildWithMargins来测量每一个子视图的宽度和高度,并且找到这些子视图的最大宽度和高度值,保存在变量maxWidth和maxHeight 中。

       FrameLayout类的成员函数onMeasure接着再将前面得到的宽度maxWidth和高度maxHeight分别加上当前视图所设置的Padding值,其中,(mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom )表示当前视图的内容区域的左右上下四条边分别到当前视图的左右上下四条边的距离,它们是父类View的四个成员变量,(mForegroundPaddingLeft,mForegroundPaddingRight,mForegroundPaddingTop,mForegroundPaddingBottom)表示当前视图的各个子视图所围成的区域的左右上下四条边到当前视视的前景区域的左右上下四条边的距离。从这里就可以看出,当前视图的内容区域的大小就等于前景区域的大小,而前景区域的大小大于等于各个子视图的所围成的区域,这是因为前景区域本来就是用来覆盖各个子视图所围成的区域的。

      加上各个Padding值之后,得到的宽度maxWidth和高度maxHeight还不是最终的宽度和高度,还需要考虑以下两个因素:

      1. 当前视图是否设置有最小宽度和高度。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

      2. 当前视图是否设置有前景图。如果设置有的话,并且它们比前面计算得到的宽度maxWidth和高度maxHeight还要大,那么就将它们作为当前视图的宽度和高度值。

      经过上述两步检查之后,FrameLayout类的成员函数onMeasure就得到了当前视图的宽度maxWidth和高度maxHeight。由于得到的宽度和高度又必须要限制在参数widthMeasureSpec和heightMeasureSpec所描述的宽度和高度规范之内,因此,FrameLayout类的成员函数onMeasure就会调用从View类继承下来的成员函数resolveSize来获得正确的大小。得到了当前视图的正确大小之后,FrameLayout类的成员函数onMeasure就可以调用从父类View继承下来的成员函数setMeasuredDimension来将它们为当前视图的大小了。

      为了理解参数widthMeasureSpec和heightMeasureSpec的含义,我们继续分析View类的成员函数resolveSize的实现,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     public  static  int  resolveSize( 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:
             result = Math.min(size, specSize);
             break ;
         case  MeasureSpec.EXACTLY:
             result = specSize;
             break ;
         }
         return  result;
     }
     ......
}

       这个函数定义在文件rameworks/base/core/java/android/view/View.java中。


       参数measureSpec的值其实是由两部分内容来组成的,最高2位表示一个测量规范,而低30位表示一个宽度值或者高度值。测量规范有三种,分别是0、1和2,使用常量MeasureSpec.UNSPECIFIED、MeasureSpec.EXACTLY和MeasureSpec.AT_MOST来表示。

       当参数measureSpec描述的规范是MeasureSpec.UNSPECIFIED时,就表示当前视图没有指定它的大小测量模式,这时候就使用参数size的值;当参数measureSpec描述的规范是MeasureSpec.AT_MOST时,就表示当前视图的大小等于参数size和参数measureSpec所指定的值中的较小值;当参数measureSpec描述的规范是MeasureSpec.EXACTLY时,就表示当前视图的大小等于参数measureSpec中所指定的值。

       回到FrameLayout类的成员函数onMeasure中,我们再来看一下View类的成员函数setMeasuredDimension是如何设置当前视图的大小的,如下所示:

1
2
3
4
5
6
7
8
9
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     protected  final  void  setMeasuredDimension( int  measuredWidth,  int  measuredHeight) {
         mMeasuredWidth = measuredWidth;
         mMeasuredHeight = measuredHeight;
         mPrivateFlags |= MEASURED_DIMENSION_SET;
     }
     ......
}

     这个函数定义在文件rameworks/base/core/java/android/view/View.java中。


      View类的成员函数setMeasuredDimension首先将参数measuredWidth和measuredHeight的值保存在成员变量mMeasuredWidth和mMeasuredHeight中,用来作为当前视图的宽度和高度,并且将成员变量mPrivateFlags的位MEASURED_DIMENSION_SET设置为1,这样返回到前面的Step 1时,就不会抛出一个类型为IllegalStateException的异常了。

       FrameLayout类的另一个成员函数measureChildWithMargins是从父类ViewGroup继承下来的,接下来我们就继续分析它的实现,以便可以了解一个视图容器的各个子视图的大小的测量过程。

       Step 3. ViewGroup.measureChildWithMargins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  abstract  class  ViewGroup  extends  View  implements  ViewParent, ViewManager {
     ......
     protected  void  measureChildWithMargins(View child,
             int  parentWidthMeasureSpec,  int  widthUsed,
             int  parentHeightMeasureSpec,  int  heightUsed) {
         final  MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
         final  int  childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                         + widthUsed, lp.width);
         final  int  childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                         + heightUsed, lp.height);
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
     ......
}

      这个函数定义在文件rameworks/base/core/java/android/view/ViewGroup.java中。


       参数child用来描述当前要测量大小的子视图,参数parentWidthMeasureSpec和parentHeightMeasureSpec用来描述当前子视图可以获得的最大宽度和高度,参数widthUsed和heightUsed用来描述父窗口已经使用了的宽度和高度。ViewGroup类的成员函数measureChildWithMargins必须要综合考虑上述参数,以及当前正在测量的子视图child所设置的大小和Margin值,还有当前视图容器所设置的Padding值,来得到当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec,这是通过调用ViewGroup类的另外一个成员函数getChildMeasureSpec来实现的。

      得到了当前正在测量的子视图child的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,就可以调用它的成员函数measure来设置它的大小了,即执行前面Step 1的操作。注意,如果当前正在测量的子视图child描述的也是一个视图容器,那么它又会重复执行Step 2和Step 3的操作,直到它的所有子孙视图的大小都测量完成为止。

      至此,我们就分析完成Android应用程序窗口的测量过程了,接下来我们继续分析Android应用程序窗口的布局过程。

      2. Android应用程序窗口的布局过程

      DecorView类的成员函数layout是从父类View继承下来的,因此,我们就从View类的成员函数layout开始分析应用程序窗口的布局过程,如图3所示:


图3 Android应用程序窗口的布局过程

        这个过程可以分为5个步骤,接下来我们就详细地分析每一个步骤。

        Step 1. View.layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
                                                                                                                                                                                     
     int  mPrivateFlags;
     ......
     public  final  void  layout( int  l,  int  t,  int  r,  int  b) {
         boolean  changed = setFrame(l, t, r, b);
         if  (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
             ......
             onLayout(changed, l, t, r, b);
             mPrivateFlags &= ~LAYOUT_REQUIRED;
         }
         mPrivateFlags &= ~FORCE_LAYOUT;
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

       参数l、t、r和b分别用来描述当前视图的左上右下四条边与其父视图的左上右下四条边的距离,这样当前视图通过这四个参数就可以知道它在父视图中的位置以及大小。

       View类的成员函数layout首先调用另外一个成员函数setFrame来设置当前视图的位置以及大小。设置完成之后,如果当前视图的大小或者位置与上次相比发生了变化,那么View类的成员函数setFrame的返回值changed就会等于true。在这种情况下, View类的成员函数layout就会继续调用另外一个成员函数onLayout重新布局当前视图的子视图。此外,如果此时View类的成员变量mPrivateFlags的LAYOUT_REQUIRED位不等于0,那么也表示当前视图需要重新布局它的子视图,因此,这时候View类的成员函数layout也会调用另外一个成员函数onLayout。

       当前视图的子视图都重新布局完成之后,View类的成员函数layout就可以将成员变量mPrivateFlags的LAYOUT_REQUIRED位设置为0了,因为此时当前视图及其子视图都已经执行了一次布局操作了。

       View类的成员函数layout最后还会将成员变量mPrivateFlags的FORCE_LAYOUT位设置为0,也是因为此时当前视图及其子视图的布局已经是最新的了。

       接下来,我们就继续分析View类的成员函数setFrame和onLayout的实现,以便可以了解当前视图及其子视图是如何执行布局操作的。

       Step 2. View.setFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
                                                                                                                                                                                 
     int  mPrivateFlags;
     ......
     int  mViewFlags;
     ......
     protected  int  mLeft;
     ......
     protected  int  mRight;
     ......
     protected  int  mTop;
     ......
     protected  int  mBottom;
     ......
     private  boolean  mBackgroundSizeChanged;
     ......
     protected  boolean  setFrame( int  left,  int  top,  int  right,  int  bottom) {
         boolean  changed =  false ;
         ......
         if  (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
             changed =  true ;
             // Remember our drawn bit
             int  drawn = mPrivateFlags & DRAWN;
             // Invalidate our old position
             invalidate();
             int  oldWidth = mRight - mLeft;
             int  oldHeight = mBottom - mTop;
             mLeft = left;
             mTop = top;
             mRight = right;
             mBottom = bottom;
             mPrivateFlags |= HAS_BOUNDS;
             int  newWidth = right - left;
             int  newHeight = bottom - top;
             if  (newWidth != oldWidth || newHeight != oldHeight) {
                 onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
             }
             if  ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                 // If we are visible, force the DRAWN bit to on so that
                 // this invalidate will go through (at least to our parent).
                 // This is because someone may have invalidated this view
                 // before this call to setFrame came in, therby clearing
                 // the DRAWN bit.
                 mPrivateFlags |= DRAWN;
                 invalidate();
             }
             // Reset drawn bit to original value (invalidate turns it off)
             mPrivateFlags |= drawn;
             mBackgroundSizeChanged =  true ;
         }
         return  changed;
     }
     ......
}

      这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

      View类的成员变量mLeft、mRight、mTop和mBottom分别用来描述当前视图的左右上下四条边与其父视图的左右上下四条边的距离,如果它们的值与参数left、right、top和bottom的值不相等,那么就说明当前视图的大小或者位置发生变化了。这时候View类的成员函数setFrame就需要将参数left、right、top和bottom的值分别记录在成员变量mLeft、mRight、mTop和mBottom中。在记录之前,还会执行两个操作:

      1. 将成员变量mPrivateFlags的DRAWN位记录在变量drawn中,并且调用另外一个成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行。如果已经执行了的话,那么就会再请求执行一个UI绘制操作,以便可以在修改当前视图的大小和位置之前,将当前视图在当前位置按照当前大小显示一次。在接下来的Step 3中,我们再详细分析View类的成员函数invalidate的实现。

      2. 计算当前视图上一次的宽度oldWidth和oldHeight,以便接下来可以检查当前视图的大小是否发生了变化。

      当前视图距离父视图的边距一旦设置好之后,它就是一个具有边界的视图了,因此,View类的成员函数setFrame接着还会将成员变量mPrivateFlags的HAS_BOUNDS设置为1。

      View类的成员函数setFrame再接下来又会计算当前视图新的宽度newWidth和高度newHeight,如果它们与上一次的宽度oldWidth和oldHeight的值不相等,那么就说明当前视图的大小发生了变化,这时候就会调用另外一个成员函数onSizeChanged来让子类有机会处理这个变化事件。

      View类的成员函数setFrame接下来继续判断当前视图是否是可见的,即成员变量mViewFlags的VISIBILITY_MASK位的值是否等于VISIBLE。如果是可见的话,那么就需要将成员变量mPrivateFlags的DRAWN位设置为1,以便接下来可以调用另外一个成员函数invalidate来成功地执行一次UI绘制操作,目的是为了将当前视图马上显示出来。

      View类的成员变量mPrivateFlags的DRAWN位描述的是当前视图上一次请求的UI绘制操作是否已经执行过了。如果它的值等于1,就表示已经执行过了,否则的话,就表示还没在等待执行。前面第一次调用View类的成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行时,如果发现已经执行了,那么就会重新请求执行一次新的UI绘制操作,这时候会导致当前视图的成员变量mPrivateFlags的DRAWN位重置为0。注意,新请求执行的UI绘制只是为了在修改当前视图的大小以及大小之前,先将它在上一次设置的大小以及位置中绘制出来,这样就可以使得当前视图的大小以及位置出现平滑的变换。换句话说,新请求执行的UI绘制只是为了获得一个中间效果,它不应该影响当前视图的绘制状态,即不可以修改当前视图的成员变量mPrivateFlags的DRAWN位。因此,我们就需要在前面第一次调用View类的成员函数invalidate前,先将当前视图的成员变量mPrivateFlags的DRAWN位保存下来,即保存在变量drawn中,然后等到调用之后,再将变量drawn的值恢复到当前视图的成员变量mPrivateFlags的DRAWN位中去。

       另一方面,如果当前视图的大小和位置发生了变化,View类的成员函数setFrame还会将成员变量mBackgroundSizeChanged的值设置为true,以便可以表示当前视图的背景大小发生了变化。

       最后,View类的成员函数setFrame将变量changed的值返回给调用者,以便调用者可以知道当前视图的大小和位置是否发生了变化。

       接下来,我们继续分析View类的成员函数invalidate的实现,以便可以了解当前视图是如何执行一次UI绘制操作的。

       Step 3. View.invalidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  class  View  implements  Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
     ......
     protected  ViewParent mParent;
     ......
                                                                                                                                                                             
     int  mPrivateFlags;
     ......
     public  void  invalidate() {
         ......
         if  ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
             mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
             final  ViewParent p = mParent;
             final  AttachInfo ai = mAttachInfo;
             if  (p !=  null  && ai !=  null ) {
                 final  Rect r = ai.mTmpInvalRect;
                 r.set( 0 0 , mRight - mLeft, mBottom - mTop);
                 // Don\\\'t call invalidate -- we don\\\'t want to internally scroll
                 // our own bounds
                 p.invalidateChild( this , r);
             }
         }
     }
     ......
}

      这个函数定义在文件frameworks/base/core/java/android/view/View.java中。


      View类的成员函数invalidate首先检查成员变量mPrivateFlags的DRAWN位和HAS_BOUNDS位是否都被设置为1。如果是的话,那么就说明当前视图上一次请求执行的UI绘制操作已经执行完成了,这时候View类的成员函数invalidate才可以请求执行新的UI绘制操作。

       View类的成员函数invalidate在请求新的UI绘制操作之前,会将成员变量mPrivateFlags的DRAWN位和DRAWING_CACHE_VALID位重置为0,其中,后者表示当前视图正在缓存的一些绘图对象已经失效了,这是因为接下来就要重新开始绘制当前视图的UI了。

       请求绘制当前视图的UI是通过调用View类的成员变量mParent所描述的一个ViewParent接口的成员函数invalidateChild来实现的。前面我们假设当前视图是应用程序窗口的顶层视图,即它是一个类型为DecoreView的视图,它的成员变量mParent指向的是与其所关联的一个ViewRoot对象。因此,绘制当前视图的UI的操作实际上是通过调用ViewRoot类的成员函数invalidateChild来实现的。

      注意,在调用ViewRoot类的成员函数invalidateChild的成员函数invalidateChild来绘制当前视图的UI之前,会将当前视图即将要绘制的区域记录在View类的成员变量mAttachInfo所描述的一个AttachInfo对象的成员变量mTmpInvalRect中。

      接下来,我们就继续分析ViewRoot类的成员函数invalidateChild的实现。

      Step 4. ViewRoot.invalidateChild

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public  final  class  ViewRoot  extends  Handler  implements  ViewParent,
         View.AttachInfo.Callbacks {
     ......
     public  void  invalidateChild(View child, Rect dirty) {
         checkThread();
         ......
         if  (mCurScrollY !=  0  || mTranslator !=  null ) {
             mTempRect.set(dirty);
             dirty = mTempRect;
             if  (mCurScrollY !=  0 ) {
                dirty.offset( 0 , -mCurScrollY);
             }
             if  (mTranslator !=  null ) {
                 mTranslator.translateRectInAppWindowToScreen(dirty);
             }
             if  (mAttachInfo.mScalingRequired) {
                 dirty.inset(- 1 , - 1 );
             }
         }
         mDirty.union(dirty);
         if  (!mWillDrawSoon) {
             scheduleTraversals();
         }
     }
     ......
}

       这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。





本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/1242900,如需转载请自行联系原作者
目录
相关文章
|
2月前
|
IDE Java 开发工具
深入探索安卓应用开发:从环境搭建到第一个"Hello, World!"应用
本文将引导读者完成安卓应用开发的初步入门,包括安装必要的开发工具、配置开发环境、创建第一个简单的安卓项目,以及解释其背后的一些基本概念。通过一步步的指导和解释,本文旨在为安卓开发新手提供一个清晰、易懂的起点,帮助读者顺利地迈出安卓开发的第一步。
210 65
|
2月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
2天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
8 2
|
5天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
25 5
|
5天前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
15天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
36 4
|
15天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
36 2
|
25天前
|
JSON API Android开发
探索安卓开发之旅:打造你的第一个天气应用
在这篇文章中,我们将一起踏上一段激动人心的旅程,学习如何在安卓平台上开发一个简单的天气应用。通过实际操作和代码示例,我们将逐步构建一个能够显示当前位置天气情况的应用。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供清晰的指导和启发性的见解,帮助你理解和掌握安卓开发的基础知识。让我们一起探索代码的世界,解锁新技能,实现你的创意和梦想。
|
28天前
|
安全 Java API
Java 泛型在安卓开发中的应用
在Android开发中,Java泛型广泛应用于集合类、自定义泛型类/方法、数据绑定、适配器及网络请求等场景,有助于实现类型安全、代码复用和提高可读性。例如,结合`ArrayList`使用泛型可避免类型转换错误;自定义泛型类如`ApiResponse&lt;T&gt;`可处理不同类型API响应;RecyclerView适配器利用泛型支持多种视图数据;Retrofit结合泛型定义响应模型,明确数据类型。然而,需注意类型擦除导致的信息丢失问题。合理使用泛型能显著提升代码质量和应用健壮性。
|
25天前
|
XML 数据可视化 Android开发
Android应用界面
Android应用界面中的布局和控件使用,包括相对布局、线性布局、表格布局、帧布局、扁平化布局等,以及AdapterView及其子类如ListView的使用方法和Adapter接口的应用。
13 0
Android应用界面