android View 绘制解析
2016-11-06 20:25
295 查看
在android开发过程中相信很多人对View树并不陌生,本文着重介绍android中view树是如何绘制。
通过上述这个View树的绘制,我们大致能够猜想出整个View树的绘制过程其实是一个View的递归绘制。
View绘制主要是通过 measure、layout、draw这三个方法进行绘制的,而我们通过源码知道view绘制的实现主要是在ViewRootImpl该类中实现。在改类中View的绘制流程是从ViewRootImpl的performTraversals()方法开始的。
该方法太长,这里我只截取了其中关于View绘制流程的代码;
下面我们先通过一个图是简单描述View绘制的大致流程
在这个方法内其实是很简单,内部直接调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
可以看到具体的测量过程是在mView的measure方法内实现,该方法的具体实现可以看到,
通过上述这端代码我们可以看出,内部 cacheIndex < 0 || sIgnoreMeasureCache 条件成立的情况下会直接调用
否则 调用
而对于View的测量我们知道实质上是调用了 onMeasure 这个方法;
在这个方法内部我们可以看到器内部的参数分别表示 measuredWidth, measuredHeight
由此我们可以看出View的测量是通过 getDefaultSize 方法实现的。下面我们来详细看一下 getDefaultSize 方法的内部实现。
对于我们来将,我们只需要看AT_MOST、EXACTLY 这两种模式即可,通过上述的代码我们可以看出 getDefaultSize 的返回值其实就是measureSpec中的Specsize,而specsize 就是View测量之后的大小。
以上是View的绘制过程,对于ViewGroup 我们知道除了绘制自身之外,还会去变量其内部子view然后调用子view的measure方法进行绘制。
可以看出其内部直接调用了 measureChild(child, widthMeasureSpec, heightMeasureSpec); 该方法,而该方法内部
我们可以看出其是直接调用了ViewGroup内部子view的measure方法。
至此View的测量(measure)方法我们大致了解。
下面我们以一个图示说明View的测量过程
下面我们通过api中的源码对layout过程进行详解。
我们知道当measure执行完之后会执行相应的layout过程。
在viewRootImpl类中我们在performLayout方法中可以看到
内部直接执行来layout方法,在该方法内部,
我们可以看到 changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED 当该判断成立时,内部其是是直接执行了 onLayout(changed, l, t, r, b);
该方法我们在View类中可以看到是一个空实现方法
同样在ViewGroup中
也是一个空实现方法,由此可见layout方法的具体实现是根据具体不同的布局实现的。
所以到此我们可以很清晰的了解,layout的执行过程与measure相似,但是也存在不同。
下面我们同样通过一个图示来理解layout的执行过程。
在ViewRootImpl类中
之后在draw方法内
通过drawSoftware 方法内部我们可以看出内部直接调用了mView.draw(..)方法
接下来我们只需要看mView中draw方法内部的具体作用
通过 draw方法,我们可以看出绘制view主要分这么几步,
1.绘制背景
2.绘制自己
3.绘制子元素
4.绘制装饰
下面我们主要看第三步 是如何绘制 子元素。
我们可以看到在view类中dispatchDraw 方法实质是一个空实现,这里我们可以想到作为一个具体的view如果不是容器类型,那么它本身是没有子元素的,所以我们该方法的实现只会在容器类型的控件中,下面我们看容器类型元素的父类ViewGroup
在该类中我们找到了这个方法的具体实现。
通过上述代码我们其事可以看到,内部对容器的子元素进行遍历,同时调用了drawChild(…)该方法。
而在drawChild方法中;
直接调用了 draw方法。
下面我们同样通过一个图示对draw方法对过程进行描绘,
至此本文对View的绘制流程已讲解完成,还望各位看官不吝赐教。
什么是View树
下面通过一个图示简单描述View树通过上述这个View树的绘制,我们大致能够猜想出整个View树的绘制过程其实是一个View的递归绘制。
View绘制主要是通过 measure、layout、draw这三个方法进行绘制的,而我们通过源码知道view绘制的实现主要是在ViewRootImpl该类中实现。在改类中View的绘制流程是从ViewRootImpl的performTraversals()方法开始的。
该方法太长,这里我只截取了其中关于View绘制流程的代码;
private void performTraversals() { .... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... performDraw(); ... }
下面我们先通过一个图是简单描述View绘制的大致流程
View 绘制流程第一步 measure
measure在View绘制中主要作用是用于测量,通过上述图示,我们知道measure是在perfromMeasure方法内进行调用的。那么我们先来看ViewRootImpl中 performMeasure中主要做了什么。private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
在这个方法内其实是很简单,内部直接调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
可以看到具体的测量过程是在mView的measure方法内实现,该方法的具体实现可以看到,
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { .... if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { ..... int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } .... }
通过上述这端代码我们可以看出,内部 cacheIndex < 0 || sIgnoreMeasureCache 条件成立的情况下会直接调用
onMeasure(widthMeasureSpec, heightMeasureSpec);
否则 调用
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
而对于View的测量我们知道实质上是调用了 onMeasure 这个方法;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在这个方法内部我们可以看到器内部的参数分别表示 measuredWidth, measuredHeight
由此我们可以看出View的测量是通过 getDefaultSize 方法实现的。下面我们来详细看一下 getDefaultSize 方法的内部实现。
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; }
对于我们来将,我们只需要看AT_MOST、EXACTLY 这两种模式即可,通过上述的代码我们可以看出 getDefaultSize 的返回值其实就是measureSpec中的Specsize,而specsize 就是View测量之后的大小。
以上是View的绘制过程,对于ViewGroup 我们知道除了绘制自身之外,还会去变量其内部子view然后调用子view的measure方法进行绘制。
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]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
可以看出其内部直接调用了 measureChild(child, widthMeasureSpec, heightMeasureSpec); 该方法,而该方法内部
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); }
我们可以看出其是直接调用了ViewGroup内部子view的measure方法。
至此View的测量(measure)方法我们大致了解。
下面我们以一个图示说明View的测量过程
View 绘制流程第二步 layout
layout的作用主要是ViewGroup用来确定子元素的位置,当ViewGroup位置确定之后它在onLayout方法中会遍历所有子元素并调用其layoug方法来确定View本身的位置。下面我们通过api中的源码对layout过程进行详解。
我们知道当measure执行完之后会执行相应的layout过程。
在viewRootImpl类中我们在performLayout方法中可以看到
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... final View host = mView; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... }
内部直接执行来layout方法,在该方法内部,
public void layout(int l, int t, int r, int b) { ... int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... } } } .. }
我们可以看到 changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED 当该判断成立时,内部其是是直接执行了 onLayout(changed, l, t, r, b);
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
该方法我们在View类中可以看到是一个空实现方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
同样在ViewGroup中
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
也是一个空实现方法,由此可见layout方法的具体实现是根据具体不同的布局实现的。
所以到此我们可以很清晰的了解,layout的执行过程与measure相似,但是也存在不同。
下面我们同样通过一个图示来理解layout的执行过程。
View 绘制第三步 draw
draw的作用主要是用来将view绘制到屏幕上,这个方法相对于measure以及layout 比较简单,下面我们来看一下android api中是如何实现draw方法的。在ViewRootImpl类中
private void performDraw() { ... draw(fullRedrawNeeded); ... }
之后在draw方法内
private void draw(boolean fullRedrawNeeded) { .... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } .... }
通过drawSoftware 方法内部我们可以看出内部直接调用了mView.draw(..)方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... mView.draw(canvas); ... }
接下来我们只需要看mView中draw方法内部的具体作用
通过 draw方法,我们可以看出绘制view主要分这么几步,
1.绘制背景
2.绘制自己
3.绘制子元素
4.绘制装饰
public void draw(Canvas canvas) { // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } ... if (!verticalEdges && !horizontalEdges) { // Step 2, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 3, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 4, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; } }
下面我们主要看第三步 是如何绘制 子元素。
protected void dispatchDraw(Canvas canvas) { }
我们可以看到在view类中dispatchDraw 方法实质是一个空实现,这里我们可以想到作为一个具体的view如果不是容器类型,那么它本身是没有子元素的,所以我们该方法的实现只会在容器类型的控件中,下面我们看容器类型元素的父类ViewGroup
在该类中我们找到了这个方法的具体实现。
protected void dispatchDraw(Canvas canvas) { .... for (int i = 0; i < childrenCount; i++) { while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } }
通过上述代码我们其事可以看到,内部对容器的子元素进行遍历,同时调用了drawChild(…)该方法。
而在drawChild方法中;
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
直接调用了 draw方法。
下面我们同样通过一个图示对draw方法对过程进行描绘,
至此本文对View的绘制流程已讲解完成,还望各位看官不吝赐教。
相关文章推荐
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图自定义View绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(四)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android 进阶学习:Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android视图绘制流程完全解析,带你一步步深入了解View(二)