深入了解View的绘制流程
1. ViewRoot
ViewRoot是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure、layout、draw)都是通过ViewRoot完成的。当Activity对象被创建完毕后,会将DecorView添加到Window中(Window是对窗口的抽象,DecorView是一个窗口的顶级容器View,其本质是一个FrameLayout),同时会创建ViewRootImpl(ViewRoot的实现类)对象,并将ViewRootImpl与DecorView建立关联。关于ViewRoot,我们只需要知道它是联系GUI管理系统和GUI呈现系统的纽带。View的绘制流程从ViewRoot的performTraversals方法开始,经过measure、layout、draw三大过程完成对一个View的绘制工作。peformTraversal方法内部会调用measure、layout、draw这三个方法,这三个方法内部又分别调用onMeasure、onLayout、onDraw方法。
在onMeasure方法中View会对其所有的子元素执行measure过程,此时measure过程就从父容器"传递"到了子元素中,接着子元素会递归的对其子元素进行measure过程,如此反复完成对整个View树的遍历。onLayout与onDraw过程的执行流程与此类似。
measure过程决定了View的测量宽高,这个过程结束后,就可以通过getMeasuredHeight和getMeasuredWidth获得View的测量宽高了;
layout过程决定了View在父容器中的位置和View的最终显示宽高,getTop等方法可获取View的top等四个位置参数(View的左上角顶点的坐标为(left, top), 右下角顶点坐标为(right, bottom)),getWidth和getHeight可获得View的最终显示宽高(width = right - left;height = bottom - top)。
draw过程决定了View最终显示出来的样子,此过程完成后,View才会在屏幕上显示出来。
2. MeasureSpec
MeasureSpec为一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,前者指测量模式,后者指某种测量模式下的规格大小。在一个View的measure过程中,系统会将该View的LayoutParams结合父容器的“要求”生成一个MeasureSpec,这个MeasureSpec说明了应该怎样测量这个View。
(1)三种 SpecMode:
UNSPECIFIED:父容器不对View作任何要求,通常用于系统内部,表示一种测量的状态。
EXACTLY:父容器已经检测出View所需要的精确大小,这种测量模式下View的测量值就是SpecSize的值。这个SpecMode对应于LayoutParams中的match_parent和给出具体大小这两种模式。
AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于此值,可用大小取决于不同View的具体实现。这个SpecMode对应于LayoutParams中的wrap_content。
(2)对于DecorView,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同确定;对于普通View,他的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同确定。
3. View的具体绘制流程
(1)measure过程
a. DecorView的measure过程
前面我们提到过,DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout。DecorView有唯一一个子View,它是一个垂直LinearLayout,这个垂直线性布局管理器包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),我们平常用的setContentView就是设置它的子View。如下图中,我们为TilteView中添加了一个ActionBar,为ContentView中添加了一个RelativeLayout(通过setContentView方法)。
前面提到,DecorView的MeasureSpec由窗口的尺寸和自身的LayoutParams共同决定。在ViewRootImpl的measureHierarchy方法中,完成了创建DecorView的MeasureSpec的过程,相应的代码片段如下:
1 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
2 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
3 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
以上代码片段中的childXxxMeasureSpec即为DecorView的MeasureSpec,lp.width和lp.height被系统赋值为MATCH_PARENT。getRootMeasureSpec的代码如下:
1 private int getRootMeasureSpec(int windowSize, int rootDimension) {
2 int measureSpec;
3 switch (rootDimension) {
4 case ViewGroup.LayoutParams.MATCH_PARENT:
5 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
6 break;
7 case ViewGroup.LayoutParams.WRAP_CONTENT:
8 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
9 break;
10 default:
11 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
12 break;
13 }
14 return measureSpec;
15 }
上述代码中调用了makeMeasureSpec方法来获取measureSpec,而传入的rootDimension参数即为lp.width或lp.height,值为MATCH_PARENT,由此可得DecorView的MeasureSpec,其中SpecMode为EXACTLY,SpecSize为windowSize。
b. 普通View(非ViewGroup)的measure过程:
非ViewGroup的View的特点是不能有子元素,因此只需测量好自身就行。普通View的measure通过measure方法来完成:
1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
2 //....
3
4 //回调onMeasure()方法
5 onMeasure(widthMeasureSpec, heightMeasureSpec);
6
7 //more
8 }
普通View的measure方法是由ViewGroup在measureChild方法中调用的(即完成了measure过程从ViewGroup到子View的传递),ViewGroup调用其子View的measure时即传入了该子View的widthMeasureSpec和heightMeasureSpec。注意到measure是一个final方法,因此要实现自定义的measure过程,需要重写onMeasure方法,onMeasure方法源码如下:
1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
3 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
4 }
setMeasureDimension方法用于设置View的测量宽高,如果不重写此方法,默认是直接调用getDefaultSize方法获取尺寸的:
1 public static int getDefaultSize(int size, int measureSpec) {
2 int result = size;
3 int specMode = MeasureSpec.getMode(measureSpec);
4 int specSize = MeasureSpec.getSize(measureSpec);
5 switch (specMode) {
6 case MeasureSpec.UNSPECIFIED:
7 result = size;
8 break;
9 case MeasureSpec.AT_MOST:
10 case MeasureSpec.EXACTLY:
11 result = specSize;
12 break;
13 }
14 return result;
15 }
由以上代码可知,正常情况下(SpecMode为AT_MOST或EXACTLY),getDefaultSize获取的尺寸大小即为specSize。由以上代码还可知道,直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent的效果。示例如下:
1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
3 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
4 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
5 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
6 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
7 if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
8 setMeasuredDimension(mWidth, mHeight);
9 } else if (widthSpecMode == MeasureSpec.AT_MOST) {
10 setMeasuredDimension(mWidth, heightSpecSize);
11 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
12 setMeasuredDimension(widthSpecSize, mHeight);
13 }
14 }
上述示例代码中的mWidth,mHeight是为wrap_content时设定的默认宽高。这个默认宽高可根据实际需要自行设置,比如TextView在wrap_content时的默认宽高是根据其中的所有文字的宽度来设定的,从而实现正好“包裹”文字内容的效果。
c. ViewGroup的measure过程:
ViewGroup需要先完成子View的measure过程,才能完成自身的measure过程,ViewGroup的onMeasure方法根据不同的布局管理器类(LinearLayout、RelativeLayout等等)有不同的实现,比如LinearLayout的onMeasure方法代码如下:
1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2 if (mOriention == VERTICAL) {
3 measureVertical(widthMeasureSpec, heightMeasureSpec);
4 } else {
5 measureHorizontal(widthMeasureSpec, heightMeasureSpec);
6 }
7 }
measureVertical中测量子元素的主要代码如下:
1 //See how tall everyone is. Also remember max width.
2 for (int i = 0; i < count; ++i) {
3 final View child = getVirtualChildAt(i);
4 . . .
5 //Determine how big this child would like to be. If this or previous children have given a weight, //then we allow it to use all available space (and we will shrink things later if needed).
6 measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalHeight == 0 ? mTotalLength : 0);
7
8 if (oldHeight != Integer.MIN_VALUE) {
9 lp.height = oldHeight;
10 }
11
12 final int childLength = child.getMeasuredHeight();
13 final int totalLength = mTotalLength;
14 mTotalLength = Math.max(totalLength, totalLength+childHeight+lp.topMargin+lp.bottomMargin+getNextLocationOffset(child));
15 }
由上述代码可以知道,在measureVertical方法中会对每个LinearLayout中的子元素进行遍历并通过measureChildBeforeLayout方法对每个子元素执行measure过程。在measureChildBeforeLayout方法内部会调用子元素的measure方法,这样会依次让每个子元素进入measure过程。mTotalLength表示LinearLayout在竖直方向上的尺寸,每完成一个子元素的measure过程,它的值也会相应增加。测量完子元素后,LinearLayout会测量自身的大小。measureVertical中测量LinearLayout自身的主要代码如下:
1 //Add in our padding.
2 mTotalLength += mPaddingTop + mPaddingBottom;
3 int heightSize = mTotalLength;
4 //Check against our minimum height
5 heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
6 //Reconcile our calculated size with the heightMeasureSpec
7 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
8 heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
9 . . .
10 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);
对垂直的LinearLayout来说,它在水平方向的测量过程与普通View的measure过程一样,在竖直方向的measure过程如下:若该垂直LinearLayout的layout_height为match_parent或具体数值,它的measure过程与普通View一样;若该垂直LinearLayout的layout_height为wrap_content,则它竖直方向的高度为所有子元素占用的高度之和,但不能超过父容器的可用空间大小,最终高度还要考虑到其竖直方向的padding,相关的代码如下:
1 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
2 int result = size;
3 int specMode = MeasureSpec.getMode(measureSpec);
4 int specSize = MeasureSpec.getSize(measureSpec);
5 switch (speczMode) {
6 case MeasureSpec.UNSPECIFIED:
7 result = size;
8 break;
9 case MeasureSpec.AT_MOST:
10 if (specSize < size) {
11 result = specSize | MEASURED_STATE_TOO_SMALL;
12 } else {
13 result = size;
14 }
15 break;
16 case MeasureSpec.EXACTLY:
17 result = specSize;
18 break;
19 }
20 return result | (childMeasuredState & MEASURED_STATE_MASK);
21 }
ViewGroup主要通过其measureChildren方法完成其子View的measure过程,上面垂直LinearLayout中调用的measureChildBeforeLayout可以看做是measureChildren的一个“变种”,measureChildren方法代码如下:
1 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
2 final int size = mChildrenCount;
3 final View[] children = mChildren;
4 for (int i = 0; i < size; ++i) {
5 final View child = children[i];
6 if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
7 measureChild(child, widthMeasureSpec, heightMeasureSpec);
8 }
9 }
10 }
其中,measureChild方法完成对子View的measure过程:
1 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
2 final LayoutParams lp = child.getLayoutParams();
3 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
4 mPaddingLeft + mPaddingRight, lp.width);
5 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
6 mPaddingTop + mPaddingBottom, lp.height);
7 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
8 }
注意在这里,在执行child.measure方法前,就已经通过getChildMeasureSpec获取了子View的MeasureSpec。getChildMeasureSpec根据子View的LayoutParams和父容器的MeasureSpec来决定子View的MeasureSpec,getChildMeasureSpec的代码如下:
1 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
2 //这里传入的spec为ViewGroup的MeasureSpec
3 //specMode和specSize即为父容器的MeasureSpec
4 int specMode = MeasureSpec.getMode(spec);
5 int specSize = MeasureSpec.getSize(spec);
6 //padding为父容器中已使用的空间大小,size为父容器可用空间大小
7 int size = Math.max(0, specSize - padding);
8 int resultSize = 0;
9 int resultMode = 0;
10
11 switch (specMode) {
12 case MeasureSpec.EXACTLY:
13 if (childDimension >= 0) {
14 resultSize = childDimension;
15 resultMode = MeasureSpec.EXACTLY;
16 } else if (childDimension == LayoutParams.MATCH_PARENT) {
17 //子View想要和父容器一样大
18 resultSize = size;
19 resultMode = MeasureSpec.EXACTLY;
20 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
21 //子View想自己决定它的大小,但不能比父容器大
22 resultSize = size;
23 resultMode = MeasureSpec.AT_MOST;
24 }
25 break;
26
27 case MeasureSpec.AT_MOST:
28 if (childDimension >= 0) {
29 resultSize = childDimension;
30 resultMode = MeasureSpec.EXACTLY;
31 } else if (childDimension == LayoutParams.MATCH_PARENT) {
32 resultSize = size;
33 resultMode = MeasureSpec.AT_MOST;
34 } else if (childDimension == LayoutParams.WRAP_CONTENT) {
35 resultSize = size;
36 resultMode = MeasureSpec.AT_MOST;
37 }
38 break;
39
40 //Parent asked to see how big we want to be
41 case MeasureSpec.UNSPECIFIED:
42 if (childDimension >= 0) {
43 resultSize = childDimension;
44 resultMode = MeasureSpec.EXACTLY;
45 } else (childDimension == LayoutParams.MATCH_PARENT) {
46 resultSize = 0;
47 resultMode = MeasureSpec.UNSPECIFIED;
48 } else (childDimension == LayoutParams.WRAP_CONTENT) {
49 resultSize = 0;
50 resultMode = MeasureSpec.UNSPECIFIED;
51 }
52 break;
53 }
54 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
55 }
以上函数的工作过程可总结如下:
a. 当childLayoutParams指定为为具体的大小时:若parentSpecMode为EXACTLY,则childSpecMode为EXACTLY,childSpecSize为childSize(layout_width和layout_height中指定的具体大小);若parentSpecMode为AT_MOST,则childSpecMode和childSpecSize分别为EXACTLY和childSize。
b. 当childLayoutParams为match_parent时:若parentSpecMode为EXACTLY,则childSpecMode和childSpecSize分别为EXACTLY和parentSize(父容器中可用的大小);若parentSpecMode为AT_MOST,则childSpecMode和childSpecSize分别为AT_MOST和parentSize。
c. 当childLayoutParams为wrap_content时:若parentSpecMode为EXACTLY,则childSpecMode和childSpecSize分别为AT_MOST和parentSize;若parentSpecMode为AT_MOST,则childSpecMode和childSpecSize分别为AT_MOST和parentSize。
还有一点需要注意的是,View的measure过程和Activity生命周期的回调方法不是同步的,也就是不能保证在某个生命周期的回调方法中measure过程已经执行完毕。
(2)layout过程
layout过程用来确定View在父容器中的位置,因而是由父容器获取子View的位置参数后,调用child.layout方法并传入已获取的位置参数,从而完成对子View的layout。当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用其layout方法,在layout方法中子元素的onLayout又会被调用。layout方法确定先View本身的位置,再调用onLayout方法确定所有子元素的位置。layout方法如下:
1 public void layout(int l, int t, int r, int b) {
2 ……
3 int oldL = mLeft;
4 int oldT = mTop;
5 int oldB = mBottom;
6 int oldR = mRight;
7 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : set(l, t, r, b);
8 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
9 onLayout(changed, l, t, r, b);
10 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
11 ListenerInfo li = mListenerInfo;
12 if (li != null && li.mOnLayoutChangeListeners != null) {
13 ArrayList<OnLayoutChangeListener> listenersCopy =
14 (ArrayList<OnLayoutChangeListener>) li.mOnLayoutChangeListeners.clone();
15 int numListeners = listenersCopy.size();
16 for (int i = 0; i < numListeners; ++i) {
17 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
18 }
19 }
20 }
21 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
22 mPrivateFlags3 |= PFLAGS3_IS_LAID_OUT;
23 }
layout方法的大致流程:首先通过setFrame方法设定View的四个位置参数,即用传来的l、t、r、b四个参数初始化mLeft、mTop、mRight、mBottom这四个值,从而确定了该View在父容器中的位置。若位置发生改变就调用onLayout方法,onLayout方法在View类中为空,因为对子元素布局的工作只有容器View才需要做。在ViewGroup中,onLayout是一个抽象方法,因为对于不同的布局管理器类,对子元素的布局方式是不同的。比如,LinearLayout的onLayout方法如下:
1 protected void onLayout(boolean changed, int l, int t, int r, int b) {
2 if (mOriention == VERTIVAL) {
3 layoutVertical(l, t, r, b);
4 } else {
5 layoutHorizontal(l, t, r, b);
6 }
7 }
以上代码会根据LinearLayout的orientation为水平或垂直调用相应的函数来完成布局过程,这里以layoutVertical为例分析一下垂直线性布局管理器的布局过程,layoutVertical的主要代码如下:
1 void layoutVertical(int left, int top, int right, int bottom) {
2 . . .
3 final int count = getVirtualChildCount();
4 for (int i = 0; i < count; i++) {
5 final View child = getVirtualChildAt(i);
6 if (child == null) {
7 childTop += measureNullChild(i);
8 } else if (child.getVisibility() != GONE) {
9 final int childWidth = child.getMeasuredWidth();
10 final int childHeight = child.getMeasuredHeight();
11
12 final int LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
13 . . .
14 if (hasDividerBeforeChildAt(i)) {
15 childTop += mDividerHeight;
16 }
17
18 childTop += lp.topMargin;
19 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
20 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
21
22 i += getChildrenSkipCount(child, i);
23 }
24 }
25 }
以上代码中,LinearLayout会遍历它的所有子View,并调用setChildFrame方法设置子View的位置,代码中的childTop代表当前子View的top位置参数。setChildFrame方法的代码如下:
1 private void setChildFrame(View child, int left, int top, int width, int height) {
2 child.layout(left, top, left + width, top + height);
3 }
也就是说,在父容器(这里为LinearLayout)完成了对子元素位置参数(top、left、right、bottom)的获取后,会调用子元素的layout方法,并把获取到的子元素位置参数传入,从而完成对子元素的layout过程。子元素在自己的layout方法中,也会先完成对自己的布局(确定四个位置参数),再调用onLayout方法完成对其子View的布局,这样layout过程就沿着View树一层层传了下去。
layout过程完成后,便可以通过getWidth和getHeight方法获取View的最终显示宽高,这俩方法源码如下:
1 public final int getWidth() {
2 return mRight – mLeft;
3 }
1 public final int getHeight() {
2 return mBottom – mTop;
3 }
由此便可以知道,通过getMeasuredWidth/getMeasuredHeight方法获取的测量宽高与通过getWidth/getHeight方法获取的最终显示宽高的区别:即最终显示宽高是通过View的位置参数相减得到的,正常情况下应该与测量宽高相等。但如果我们重写View的layout方法如下:
1 public void layout(int l, int t, int r, int b) {
2 super.layout(l, t, r, b, r + 100, b + 100);
3 }
这样就会导致最终显示宽高比测量宽高大100。(除非你很明确的知道自己想要干啥,否则不应该这样做)
(3)draw过程
主要分为以下六步:
a. 绘制背景;
b. 如果要视图显示渐变框,这里会做一些准备工作;
c.
绘制视图本身,即调用onDraw()函数。在View中onDraw()是个空函数,也就是说具体的视图都要override该函数来实现自己的显示,而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子view已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
d.
绘制子视图,即dispatchDraw()函数。在View中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
e. 如果需要, 开始绘制渐变框;
f. 绘制滚动条;
draw方法的代码如下:
1 public void draw(Canvas canvas) {
2 final int privateFlags = mPrivateFlags;
3 final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
4 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
5 mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
6 // Step 1, draw the background, if needed
7 int saveCount;
8 if (!dirtyOpaque) {
9 drawBackground(canvas);
10 }
11 //step 2 & 5
12 final int viewFlags = mViewFlags;
13 boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
14 boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
15 if (!verticalEdges && !horizontalEdges) {
16 // Step 3, draw the content
17 if (!dirtyOpaque) onDraw(canvas);
18 // Step 4, draw the children
19 dispatchDraw(canvas);
20 // Step 6, draw decorations (scrollbars)
21 onDrawScrollBars(canvas);
22 if (mOverlay != null && !mOverlay.isEmpty()) {
23 mOverlay.getOverlayView().dispatchDraw(canvas);
24 // we're done...
25 return;
26 }
27 }
28 }
View的draw过程的传递通过diapatchDraw来实现,dispatchDraw会遍历调用所有子View的draw方法,这样draw事件就一层层传了下去。重写View的onDraw方法可以定制View绘制出来的样子,例如实现一些特殊的图形和动画。
View有个名为setWillNotDraw的方法,若一个View不需要绘制任何内容,可通过这个方法将相应标记设为true,系统会进行相应优化。ViewGroup默认开启这个标记,View默认不开启。
以上是我学习View的绘制流程后的简单总结,很多地方叙述的还不够清晰准确,如有问题欢迎大家在评论区一起讨论 :)
深入了解View的绘制流程的更多相关文章
- 深入理解 Android 之 View 的绘制流程
概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...
- 自定义控件(视图)1期笔记02:View的绘制流程
1. 引言: 来自源码的3个方法: (1)public final void measure():测量,用来控制控件的大小,final不建议覆写 (2)public void layout():布局, ...
- Android探究之View的绘制流程
Android中Activity是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图 ...
- Android的自定义View及View的绘制流程
目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...
- 【转】深入理解Android之View的绘制流程
概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...
- 自定义view:view的绘制流程
1.view的绘制流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw. ...
- 每日一问:简述 View 的绘制流程
Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...
- Android之View的绘制流程
本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...
- Android View的绘制流程
写得太好了,本来还想自己写的,奈何肚里墨水有限,直接转吧.正所谓前人种树,后人乘凉.. View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发 ...
随机推荐
- es6 Reflect对象详解
Reflect是ES6为操作对象而提供的新API,而这个API设计的目的只要有: 将Object对象的一些属于语言内部的方法放到Reflect对象上,从Reflect上能拿到语言内部的方法.如:Obj ...
- redis的主从配置
redis的主备配置比较简单,只需要在配置上新增slaveof属性即可,如果主节点需要密码验证,则在加上masterauth属性. 测试安装一个备用redis,备份前一章的节点redis的docker ...
- 网站用户行为分析——Linux的安装
Linux的选择 在Linux系统各个发行版中,CentOS系统和Ubuntu系统在服务端和桌面端使用占比最高,网络上资料最是齐全,所以建议使用CentOS系统或Ubuntu. 一般来说,如果要做服务 ...
- Python2018秋招(笔者亲身经历)
毕业即失业,苦逼的大四狗伤不起哟. 又到了一年一度的秋招了,笔者也在拉勾,智联,boss直聘注册了,投了50份简历,3个面试,然而全挂了. 笔者痛定思痛决定将自己的经历贴出,希望可以帮到要面试的同学. ...
- 第1天 Java基础语法
Java基础语法 今日内容介绍 Java开发环境搭建 HelloWorld案例 注释.关键字.标识符 数据(数据类型.常量) Java开发环境搭建 Java概述 众所周知Java是一门编程语言,编程语 ...
- 嘿,C语言(持续更新中...)
---恢复内容开始--- 上次简单介绍了一下C语言,这次说说数据与计算程序,那么话不多说,进来看看. 第二章 数据与简单的计算程序 一:数据 既然说到了数据,那么说说什么是写数据呢? 表面意 ...
- Java8 Lambda表达式实战之方法引用(一)
方法的引用 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法,方法引用提供了一种引用而不执行方法的方式,如果抽象方法的实现恰好可以使用调用另外一个方法来实现,就有可能可以使用方法引用 方法 ...
- 优步UBER司机全国各地奖励政策汇总 (2月15日-2月21日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 北京Uber优步司机奖励政策(1月21日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 广州Uber优步司机奖励政策(1月11日~1月17日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...