您的位置:首页 > 移动开发 > Android开发

android View 绘制解析

2016-11-06 20:25 295 查看
在android开发过程中相信很多人对View树并不陌生,本文着重介绍android中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的绘制流程已讲解完成,还望各位看官不吝赐教。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐