前言
自定义view基本流程,已经讲了过半,接下来讲layout 和draw。
Android坐标系前面已经讲过了,这就不再赘述。

Layout(布局)
layout是为了view及其子view分配大小和位置。这是布局的第二阶段,第一阶段上面已经说过了是measure,此时每个父view都会调用所有的子view的onLayout来放置它们。派生类不会覆盖此方法,带子view的派生类应重写此方法。这是layout源码里的注释大概意思,那么我们来看看layout具体做了什么。

/**
 * 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 */ public void layout(int l, int t, int r, int b) { 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; 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);//只有子view的才能覆盖此方法,所以此处是空实现 if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 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); } } } ... }

可以看出来,通过setOpticalFrame或者setFrame传递子view相对于父view左上右下的距离来确定位置。由于派生类不覆写此方法。所以我们还是从上一章中讲的AbsoluteLayout看onLayout的实现,虽然AbsoluteLayout是viewGroup的子类。

@Override
protected void onLayout(boolean changed, int l, int t,
        int r, int b) {
    int count = getChildCount();

    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {

            AbsoluteLayout.LayoutParams lp =
                    (AbsoluteLayout.LayoutParams) child.getLayoutParams();

            int childLeft = mPaddingLeft + lp.x;
            int childTop = mPaddingTop + lp.y;
            child.layout(childLeft, childTop,
                    childLeft + child.getMeasuredWidth(),
                    childTop + child.getMeasuredHeight());
        }
    }
}

会首先判断是否还包含子view,如果有子view还会通过调用layout确定子view在父view里的位置。递归式的回调,直到再没有子view。
Draw(绘制)
在看draw功能之前,我们先看看draw在源码里的注释。

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             instead of overriding this method.
    If you do need to override this method, call the superclass version.

前面什么意思,自己看。最后一句很重要:如果确实需要重写此方法,请调用超类版本。也就是说viewGroup里没有draw方法,那么viewGroup怎么draw自己的呢? 调用的是超类view的draw方法。那么draw都做了什么工作呢

/*
 * 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)
 */

1.画背景
2.如果有需要,保存画布
3.画内容
4.画孩子
5.如果需要,恢复保存的涂层
6.画装饰

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    // Step 1, draw the background, if needed
int saveCount;


drawBackground(canvas);


// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
    // Step 3, draw the content
    onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    drawAutofilledHighlight(canvas);

    // Overlay is part of the content and draws beneath Foreground
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);

    // Step 7, draw the default focus highlight
    drawDefaultFocusHighlight(canvas);

    if (debugDraw()) {
        debugDrawFocus(canvas);
    }

    // we're done...
    return;
}    
...
}

这是view的draw过程。当然viewGroup的过程也是类似。只不过view里的dispatchDraw是空实现,而viewGroup里的dispatchDraw则是遍历子view后调用drawChild完成绘制子view。继承自viewGroup的view也是通过重写dispatchDraw完成绘制。

以上则是draw流程大概的工作内容。感兴趣的朋友,可以深究下源码。研究到这基本对自定义view够用了。

发表评论

邮箱地址不会被公开。