绘制layout布局 android,带着问题学习Android中View的layout布局和draw绘制

在博客带着问题学习Android中View的measure测量中,我们学习了view的measure测量的原理,以及解决了我们实际项目中遇到的一些问题的解决方案。在这篇博客中我们同样根据问题去学习下layout和draw的源码。

引导问题

在自定义view中为什么onMeasure()方法会调用多次?目的是什么?

在自定义view中draw()的绘制步骤是什么?

layout方法源码分析

概述

在自定义view/viewGroup中,我们一般需要重写onLayout方法进行布局,在这个方法中我们可以根据我们的需求对view的位置或者viewgroup中的子view的位置进行设定。我们在view的源码中查看下:

/**

* Called from layout when this view should

* assign a size and position to each of its children.

*

* Derived classes with children should override

* this method and call layout on each of

* their children.

* @param changed This is a new size or position for this view

* @param left Left position, relative to parent

* @param top Top position, relative to parent

* @param right Right position, relative to parent

* @param bottom Bottom position, relative to parent

*/

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

我们看到这个protected方法是一个空方法,一行代码也没有,这就需要我们进行重写该方法,然后实现我们的布局效果。那么这个方法在哪里执行的?就需要我们查找看看。

layout源码分析

经过查找,我们发现在layout()方法中调用了我们重写的onLayout()方法,然后实现我们自定义的布局效果。我们看下源码:

/** * Assign a size and position to a view and all of its * descendants * *

This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().

* *

Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.

* *@param l Left position, relative to parent *@param t Top position, relative to parent *@param r Right position, relative to parent *@param b Bottom position, relative to parent */

@SuppressWarnings({"unchecked"})

public void layout(int l, int t, int r, int b) {

//判断layout之前是否需要进行measure,这里使用位运算符,

//PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT是一个常量,用于标志在layout()之前

//是否需要进行measure(),所以有时onMeasure方法调用多次

if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {

onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);

mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

}

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

//判断view的父view的layoutMode属性是否为LAYOUT_MODE_OPTICAL_BOUNDS,

//然后调用set***Frame()方法进行控件的位置大小信息,以此判断控件大小是否改变

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

//如果view的大小发生改变或者我们主动要求布局的时候,开始进行重新布局,调用我们

//重写的onLayout方法

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

onLayout(changed, l, t, r, b);

//改变标志位

mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

//判断view中的layoutChange改变的监听事件是否为null

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLayoutChangeListeners != null) {

ArrayList listenersCopy =

(ArrayList)li.mOnLayoutChangeListeners.clone();

int numListeners = listenersCopy.size();

for (int i = 0; i < numListeners; ++i) {

listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);

}

}

}

//改变状态位

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

}

我们看下源码中的针对layout方法的解释:“为一个view的大小和位置进行赋值或者它的子项”。layout是布局机制的第二阶段,第一阶段是measure。在这个阶段parent调用layout方法放置他们的子view。view的派生类不能重写layout方法,派生类需要重写onLayout方法,在onL方法中调用layout方法来设置子view的的位置。关于参数:l、t、r、b这四个参数是相对于parent view的位置。

上面代码的注释也还比较清晰,我们主要讲解下changed变量,该变量标识着我们的view大小是否改变。我们看下系统是怎么判断的。这就涉及到这段代码:

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

代码中涉及到两个方法setOpticalFrame和setFrame,我们先看下setFrame方法。

/** * Assign a size and position to this view. * * This is called from layout. * *@param left Left position, relative to parent *@param top Top position, relative to parent *@param right Right position, relative to parent *@param bottom Bottom position, relative to parent *@return true if the new size and position are different than the * previous ones * {@hide} */

protected boolean setFrame(int left, int top, int right, int bottom) {

boolean changed = false;

if (DBG) {

Log.d("View", this + " View.setFrame(" + left + "," + top + ","

+ right + "," + bottom + ")");

}

//判断view的位置大小是否改变,通过比较现在的值和以前存储的值进行比较

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {

//view大小改变,改变标识的值为true

changed = true;

// Remember our drawn bit

int drawn = mPrivateFlags & PFLAG_DRAWN;

//计算view的旧长宽和本次的长宽值

int oldWidth = mRight - mLeft;

int oldHeight = mBottom - mTop;

int newWidth = right - left;

int newHeight = bottom - top;

//判断view的长宽大小 是否改变

boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

// Invalidate our old position,刷新视图

invalidate(sizeChanged);

//存储本次的left、top、right、bottom值为旧值

mLeft = left;

mTop = top;

mRight = right;

mBottom = bottom;

//设置l、t、b、r的值

mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

mPrivateFlags |= PFLAG_HAS_BOUNDS;

//如果view大小改变,调用sizeChange方法

if (sizeChanged) {

sizeChange(newWidth, newHeight, oldWidth, oldHeight);

}

if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {

// 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, thereby clearing

// the DRAWN bit.

mPrivateFlags |= PFLAG_DRAWN;

invalidate(sizeChanged);

// parent display list may need to be recreated based on a change in the bounds

// of any child

invalidateParentCaches();

}

// Reset drawn bit to original value (invalidate turns it off)

mPrivateFlags |= drawn;

mBackgroundSizeChanged = true;

notifySubtreeAccessibilityStateChangedIfNeeded();

}

return changed;

}

实现的原理就是根据本次的l、r、t、b值与上次的值进行比较,判断view的位置大小是否改变,如果view的size改变,调用sizeChange()方法。我们看看这个方法里的东东。

private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {

onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);

if (mOverlay != null) {

mOverlay.getOverlayView().setRight(newWidth);

mOverlay.getOverlayView().setBottom(newHeight);

}

rebuildOutline();

}

在这个方法里调用了onSizeChanged()方法,这个方法就是我们自定义view时候,肯能需要重写的onSizeChanged()方法,用于处理我们对view大小改变的处理。这里涉及到一个知识点ViewOverlay这个类,可能很多人不了解,可以看下这个教程,ViewOverlay它是view的最上面的一个透明的层,我们可以在这个层之上添加内容而不会影响到整个布局结构。这个层和我们的界面大小相同,可以理解成一个浮动在界面表面的二维空间。sizeChange方法中判断当前view的ViewOverlay对象是否为null,如果不为null,就进行设置view相对parentView的right值和bottom值。具体涉及可以去看下setRight和setBottom的源码,当然还有setTop和setLeft两个方法。我们就仅仅看下setRight的源码,其它几个类似。

/** * Sets the right position of this view relative to its parent. This method is meant to be called * by the layout system and should not generally be called otherwise, because the property * may be changed at any time by the layout. * *@param right The right of this view, in pixels. */

public final void setRight(int right) {

if (right != mRight) {//判断相对父view右边位置是否变化

//判断transform matrix是否是identity matrix.

final boolean matrixIsIdentity = hasIdentityMatrix();

if (matrixIsIdentity) {

//判断依附的父window是否为null

if (mAttachInfo != null) {

int maxRight;

//记录下新旧最大的right值

if (right < mRight) {

maxRight = mRight;

} else {

maxRight = right;

}

//刷新计算出的新区域

invalidate(0, 0, maxRight - mLeft, mBottom - mTop);

}

} else {

// Double-invalidation is necessary to capture view's old and new areas

invalidate(true);

}

//计算旧的宽度和高度值

int oldWidth = mRight - mLeft;

int height = mBottom - mTop;

mRight = right;

mRenderNode.setRight(mRight);

sizeChange(mRight - mLeft, height, oldWidth, height);

if (!matrixIsIdentity) {//如果不是identity matrix.强制进行刷新一次,根据sizeChange改变后的值

mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation

invalidate(true);

}

mBackgroundSizeChanged = true;

invalidateParentIfNeeded();

if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {

// View was rejected last time it was drawn by its parent; this may have changed

invalidateParentIfNeeded();

}

}

}

draw源码分析

概述

通过上面的分析,我们已经完成了自定义view中的measure和layout两大过程,下面就开始了draw的过程,用于我们绘制我们的事务。在自定义view中,我们需要重写onDraw方法进行绘制:

/** * Implement this to do your drawing. * *@param canvas the canvas on which the background will be drawn */

protected void onDraw(Canvas canvas) {

}

又是一个空方法,“实现此方法来实现我们的绘制”。在View中通过draw方法来调用我们实现的onD方法来实现绘制。接下来我们就要分析draw方法中的绘制步骤?以及何时调用我们的onD方法进行绘制。

/** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. * * @param canvas The Canvas to which the View is rendered. */

public void draw(Canvas canvas) {

final int privateFlags = mPrivateFlags;

//标识该view的背景是否不透明

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */

// Step 1, draw the background, if needed

int saveCount;

//如果是不透明,绘制背景色

if (!dirtyOpaque) {

drawBackground(canvas);

}

// skip step 2 & 5 if possible (common case)

final int viewFlags = mViewFlags;

//判断view是否设置水平边界渐变和垂直方向边界渐变,fadingEdge属性用来设置拉滚动条时边框渐变的放向。

//none(边框颜色不变),horizontal(水平方向颜色变淡),vertical(垂直方向颜色变淡)。

boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;

boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

//如果没有设置水平和垂直方向渐变,则跳过第二步和第五步

if (!verticalEdges && !horizontalEdges) {

// Step 3, draw the content,如果不透明,进行绘制

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children,分发draw函数,绘制child view

dispatchDraw(canvas);

// Step 6, draw decorations (scrollbars),绘制scrollbars

onDrawScrollBars(canvas);

//如果ViewOverlay不为空,进行分发绘制

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().dispatchDraw(canvas);

}

// we're done...

return;

}

/* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */

boolean drawTop = false;

boolean drawBottom = false;

boolean drawLeft = false;

boolean drawRight = false;

//上下左右的渐变长度

float topFadeStrength = 0.0f;

float bottomFadeStrength = 0.0f;

float leftFadeStrength = 0.0f;

float rightFadeStrength = 0.0f;

// Step 2, save the canvas' layers

int paddingLeft = mPaddingLeft;

//判断是否需要paddingOffset

final boolean offsetRequired = isPaddingOffsetRequired();

if (offsetRequired) {

paddingLeft += getLeftPaddingOffset();

}

//计算left、right、top、bottom的位置

int left = mScrollX + paddingLeft;

int right = left + mRight - mLeft - mPaddingRight - paddingLeft;

int top = mScrollY + getFadeTop(offsetRequired);

int bottom = top + getFadeHeight(offsetRequired);

if (offsetRequired) {

right += getRightPaddingOffset();

bottom += getBottomPaddingOffset();

}

final ScrollabilityCache scrollabilityCache = mScrollCache;

final float fadeHeight = scrollabilityCache.fadingEdgeLength;

int length = (int) fadeHeight;

// clip the fade length if top and bottom fades overlap

// overlapping fades produce odd-looking artifacts

if (verticalEdges && (top + length > bottom - length)) {

length = (bottom - top) / 2;

}

// also clip horizontal fades if necessary

if (horizontalEdges && (left + length > right - length)) {

length = (right - left) / 2;

}

//如果垂直渐变,进行计算渐变的起始位置长度

if (verticalEdges) {

topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));

drawTop = topFadeStrength * fadeHeight > 1.0f;

bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));

drawBottom = bottomFadeStrength * fadeHeight > 1.0f;

}

//如果水平渐变,进行计算渐变的起始长度

if (horizontalEdges) {

leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));

drawLeft = leftFadeStrength * fadeHeight > 1.0f;

rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));

drawRight = rightFadeStrength * fadeHeight > 1.0f;

}

saveCount = canvas.getSaveCount();

//获取

int solidColor = getSolidColor();

if (solidColor == 0) {

final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

if (drawTop) {

canvas.saveLayer(left, top, right, top + length, null, flags);

}

if (drawBottom) {

canvas.saveLayer(left, bottom - length, right, bottom, null, flags);

}

if (drawLeft) {

canvas.saveLayer(left, top, left + length, bottom, null, flags);

}

if (drawRight) {

canvas.saveLayer(right - length, top, right, bottom, null, flags);

}

} else {

scrollabilityCache.setFadeColor(solidColor);

}

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers

final Paint p = scrollabilityCache.paint;

final Matrix matrix = scrollabilityCache.matrix;

final Shader fade = scrollabilityCache.shader;

if (drawTop) {

matrix.setScale(1, fadeHeight * topFadeStrength);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, right, top + length, p);

}

if (drawBottom) {

matrix.setScale(1, fadeHeight * bottomFadeStrength);

matrix.postRotate(180);

matrix.postTranslate(left, bottom);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, bottom - length, right, bottom, p);

}

if (drawLeft) {

matrix.setScale(1, fadeHeight * leftFadeStrength);

matrix.postRotate(-90);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, left + length, bottom, p);

}

if (drawRight) {

matrix.setScale(1, fadeHeight * rightFadeStrength);

matrix.postRotate(90);

matrix.postTranslate(right, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(right - length, top, right, bottom, p);

}

canvas.restoreToCount(saveCount);

// Step 6, draw decorations (scrollbars)

onDrawScrollBars(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().dispatchDraw(canvas);

}

}

通过代码可以看到draw的绘制顺序:

绘制backgroud

如果有可能为了fading进行保存cavas的图层。

绘制view的内容,此时调用我们重写的onDraw方法

绘制child view

如果有可能,绘制fading edges同时restore Layout图层。

Draw decorations

我们针对draw()的源码进行了简单的注释,在view的绘制过程中,重要的两个方法就是onDraw()和dispatchDraw()两个方法。onDraw()方法是我们自定义view中自己根据需求进行编写函数。dispatchDraw()方法是我们进行绘制子view的方法。View.java中dispatchDraw()默认为空实现,因为其不包含子视图,而ViewGroup重载了dispatchDraw()来对其子视图进行绘制,通常应用程序不应该对dispatchDraw()进行重载,其默认实现体现了View系统绘制的流程。那么,接下来我们继续分析下ViewGroup中dispatchDraw()的具体流程:

@Override

protected void dispatchDraw(Canvas canvas) {

...

if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {

for (int i = 0; i < count; i++) {

final View child = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

} else {

for (int i = 0; i < count; i++) {

final View child = children[getChildDrawingOrder(count, i)];

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

}

......

}

dispatchDraw()的核心代码就是通过for循环调用drawChild()对ViewGroup的每个子视图进行绘制,上述代码中如果FLAG_USE_CHILD_DRAWING_ORDER为true,则子视图的绘制顺序通过getChildDrawingOrder来决定,默认的绘制顺序即是子视图加入ViewGroup的顺序,而我们可以重载getChildDrawingOrder函数来更改默认的绘制顺序,这会影响到子视图之间的重叠关系。

drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制,如果子视图的包含SKIP_DRAW标识,那么仅调用dispatchDraw(),即跳过子视图本身的绘制,但要绘制视图可能包含的字视图。

至此,基本完成了核心代码的分析。继续学习,感觉分析的不透彻,功力还不行啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值