Android 中View的绘制流程(结合图解及伪代码说明)
2018-04-03 15:50
579 查看
)##介绍
在Android开发过程中,经常存在需要实现自定义控件的情况,对于比较简单的需求,通过组合系统提供的原生控件既可以完成,但是一旦碰到比较复杂的控件时候,这时候就需要我们亲自动手完成控件的设计,实现对控件的测量、布局、绘制等操作,而这一且操作的前提是你需要了解并掌握View的绘制流程。
在正式讲解View的绘制流程之前,我们有必要先来简单了解下Android的UI管理系统层级关系,如下图所示:
PhoneWindow 是Android系统中最基本的窗口系统,每一个Activity会创建一个。PhoneWindow是Activity和View系统交互的接口。DecorView本质上是一个FrameLayout,是Activity中所有View的祖先。
视图的绘制可以分为三个步骤,分别为测量(Measure)、布局(Layout)和绘制(Draw)。
其框架过程如下:
MeasureSpec表示的是一个32位整型值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明如何测量这个View,其核心代码如下
小结(重点关注代码中的以下三种测量模式):
1、UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。
2、EXACTLY:精确测量模式,当该视图的layout_width或者layout_height指定为具体数值或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种模式下的View测量值就是SpecSize大小的值。
3、AT_MOST:最大值模式,当该视图的layout_width或者layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
注意:对DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。
为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
由上面我们知道,页面的测量流程是从performMeasure()方法开始的,核心代码如下
从上面可以看出。具体的测量操作是分发给ViewGroup的,由ViewGroup在它的measureChild方法中传递
measureChildren主要是遍历ViewGroup中的所有View进行测量代码如下
measureChild是为了测量某一个指定的View 重要根据父容器的MeasureSpec和子View的LayoutParams等信息计算子View的MeasureSpec
measure函数原型为 View.java 该函数不能被重载,而是通过回调onMeasure方法实现的。
onMeasure方法通常是由View特定子类自己实现的,开发者也可以通过重写这个方法实现自定义View。
//如果View没有重写onMeasure方法,则会默认直接调用getDefaultSize来获取View的宽高
小结:
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth);
2、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程。
为了大家更好的理解采用下面的伪代码
为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。该过程用来确定View在父容器中的布局位置。
由父容器获取子View的位置参数后,调用子View的layout方法并将位置参数传入实现的,ViewRootImpl的performLayout代码如下:
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
同样地, 将上面layout调用流程,用伪代码描述如下:
可以看到最终调用到每一个View的Draw方法绘制每一个具体的View,绘制基本上可以分为六个步骤,代码如下
小结
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程:
mView.draw()开始绘制,draw()方法实现的功能如下:
1、绘制该View的背景
2、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。
伪代码:
强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可 。
这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
4、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
一般引起invalidate()操作的函数如下:
1、setVisibility()方法:当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
在Android开发过程中,经常存在需要实现自定义控件的情况,对于比较简单的需求,通过组合系统提供的原生控件既可以完成,但是一旦碰到比较复杂的控件时候,这时候就需要我们亲自动手完成控件的设计,实现对控件的测量、布局、绘制等操作,而这一且操作的前提是你需要了解并掌握View的绘制流程。
在正式讲解View的绘制流程之前,我们有必要先来简单了解下Android的UI管理系统层级关系,如下图所示:
PhoneWindow 是Android系统中最基本的窗口系统,每一个Activity会创建一个。PhoneWindow是Activity和View系统交互的接口。DecorView本质上是一个FrameLayout,是Activity中所有View的祖先。
绘制整体流程
当一个应用启动的时候,会启动一个主Activity,Android系统会根据Activity的布局来对它进行绘制。绘制会从根视图ViewRootImpl的performTraversals()方法开始,从上到下遍历整个视图树,每一个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。整个流程如下图所示:视图的绘制可以分为三个步骤,分别为测量(Measure)、布局(Layout)和绘制(Draw)。
private void performTraversals() { int childWidthMeasureSpec=getRootMeasureSpec(mWidth,lp.width); int childHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height); ......... //执行测量流程 performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); ......... //执行布局流程 performLayout(lp,desiredWindowWidth,desiredWindowHeight); ......... //执行绘制流程 performDraw(); }
其框架过程如下:
MeasureSpec
在介绍Measure过程之前,我们先了解一下MeasureSpec,这对之后理解Measure过程是十分重要的!MeasureSpec表示的是一个32位整型值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec是View类的一个静态内部类,用来说明如何测量这个View,其核心代码如下
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface M 10b5b easureSpecMode {} //不指定测量模式 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //精确测量模式 public static final int EXACTLY = 1 << MODE_SHIFT; //最大值测量模式 public static final int AT_MOST = 2 << MODE_SHIFT; //根据指定的大小和模式创建一个MeasureSpec public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } //获取测量模式 @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } //获取测量大小 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } //微调某一个MeasureSpec的大小 static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } }
小结(重点关注代码中的以下三种测量模式):
1、UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。
2、EXACTLY:精确测量模式,当该视图的layout_width或者layout_height指定为具体数值或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种模式下的View测量值就是SpecSize大小的值。
3、AT_MOST:最大值模式,当该视图的layout_width或者layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
注意:对DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。
Measure过程
主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
由上面我们知道,页面的测量流程是从performMeasure()方法开始的,核心代码如下
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { .......... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); .......... }
从上面可以看出。具体的测量操作是分发给ViewGroup的,由ViewGroup在它的measureChild方法中传递
measureChildren主要是遍历ViewGroup中的所有View进行测量代码如下
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]; // 当View的可见性处于Gone状态时,不对其进行测量 if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
measureChild是为了测量某一个指定的View 重要根据父容器的MeasureSpec和子View的LayoutParams等信息计算子View的MeasureSpec
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); }
measure函数原型为 View.java 该函数不能被重载,而是通过回调onMeasure方法实现的。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ......... onMeasure(widthMeasureSpec,heightMeasureSpec); ......... }
onMeasure方法通常是由View特定子类自己实现的,开发者也可以通过重写这个方法实现自定义View。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
//如果View没有重写onMeasure方法,则会默认直接调用getDefaultSize来获取View的宽高
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; }
小结:
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth);
2、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程。
为了大家更好的理解采用下面的伪代码
//回调View视图里的onMeasure过程 private void onMeasure(int height , int width){ //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight) //1、该方法必须在onMeasure调用,否者报异常。 setMeasuredDimension(h , l) ; //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个measure()过程就是个递归过程 //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都 measureChildWithMargins(child , h, i) ; //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下: //child.measure(h, l) } } //该方法具体实现在ViewGroup.java里 。 protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }
Layout过程
主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。该过程用来确定View在父容器中的布局位置。
由父容器获取子View的位置参数后,调用子View的layout方法并将位置参数传入实现的,ViewRootImpl的performLayout代码如下:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ........ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ........ }
//View.java public void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴 ........ onLayout(changed, l, t, r, b); ........ }
// 空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View控件布局流程 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
同样地, 将上面layout调用流程,用伪代码描述如下:
// layout()过程 ViewRoot.java // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout() private void performTraversals(){ //... View mView ; mView.layout(left,top,right,bottom) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void onLayout(int left , int top , right , bottom){ //如果该View不是ViewGroup类型 //调用setFrame()方法设置该控件的在父视图上的坐标轴 setFrame(l ,t , r ,b) ; //-------------------------- //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个layout()过程就是个递归过程 child.layout(l, t, r, b) ; } }
Draw 过程
Draw操作用来将控件绘制出来,绘制的流程是从performDraw方法开始,核心代码如下。private void performDraw(){ ...... draw(fullRedrawNeeded); ...... } private void draw(boolean fullRedrawNeeded) { ........ if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } ........ } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ....... mView.draw(canvas); ....... }
可以看到最终调用到每一个View的Draw方法绘制每一个具体的View,绘制基本上可以分为六个步骤,代码如下
public void draw(Canvas canvas) { ........ //步骤一:绘制View的背景 drawBackground(canvas); ......... //步骤二:如果需要的话,保存canvans的图层,为fading做准备 saveCount = canvas.getSaveCount(); ........ canvas.saveLayer(left, top, right, top + length, null, flags); //步骤三:绘制View的内容 onDraw(canvas); ..... //步骤四:绘制View的子View dispatchDraw(canvas); //步骤五:如果需要的话,绘制View的fading边缘并恢复图层 canvas.drawRect(right - length, top, right, bottom, p); ...... canvas.restoreToCount(saveCount); ...... //步骤六:绘制View的装饰(比如滚动条) onDrawScrollBars(canvas); }
小结
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程:
mView.draw()开始绘制,draw()方法实现的功能如下:
1、绘制该View的背景
2、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方“需要重绘”的视图才会调用draw()方法)。
伪代码:
// draw()过程 ViewRoot.java // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图 private void draw(){ //... View mView ; mView.draw(canvas) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void draw(Canvas canvas){ //该方法会做如下事情 //1 、绘制该View的背景 //2、为绘制渐变框做一些准备操作 //3、调用onDraw()方法绘制视图本身 //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。 // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。 //5、绘制渐变框 } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 @Override protected void dispatchDraw(Canvas canvas) { // //其实现方法类似如下: int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ View child = getChildAt(i) ; //调用drawChild完成 drawChild(child,canvas) ; } } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 protected void drawChild(View child,Canvas canvas) { // .... //简单的回调View对象的draw()方法,递归就这么产生了。 child.draw(canvas) ; //......... }
强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可 。
这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。
invalidate()方法
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
4、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
requestLayout()方法
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括该调用者本身。一般引起invalidate()操作的函数如下:
1、setVisibility()方法:当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
requestFocus()方法
说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。总结
至此View绘制流程基本讲述完毕,为了更好的巩固这些知识,可以参考我的另一篇文章Android中自定义控件之流式布局实现方式相关文章推荐
- Android View绘制流程(结合源码分析)上
- Android视图的绘制流程(下)——View的Layout与Draw过程
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android View绘制流程
- Android View绘制(二)-绘制流程分发
- Android视图的绘制流程(下)——View的Layout与Draw过程
- Android应用层View绘制流程与源码分析
- Android View的绘制流程 (http://www.jianshu.com/p/5a71014e7b1b)
- Android中View绘制流程以及invalidate()等相关方法分析(转)
- android的view绘制流程
- Android 自定义view ViewRootImpl绘制流程
- Android View绘制流程完全解析(一)
- Android中View绘制流程以及invalidate()等相关方法分析(转)
- Android应用层View绘制流程之measure,layout,draw三步曲
- Android中View绘制流程以及invalidate()等相关方法分析
- Android中View绘制流程以及invalidate()等相关方法分析
- Android view的绘制流程(一)
- 深入理解 Android 之 View 的绘制流程(三)_Layout
- Android中View绘制流程以及invalidate()等相关方法分析
- Android视图绘制流程完全解析,带你一步步深入了解View(二)