简书链接:View绘制流程
文章字数:419,阅读全文大约需要1分钟

image.png
1、Measure
MeasureSpec:在Measure流程中,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,
在onMeasure中根据这个MeasureSpec来确定view的测量宽高

测量模式
EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小
——match_parent,精确值

    ATMOST : child view最终的大小不能超过父容器的给的
            ------wrap_content 
            
    UNSPECIFIED: 不确定,源码内部使用
            -------一般在ScrollView,ListView 

    2)、测量大小:根据测量模式来确定测量大小

    3)源码里面的位运算
    &:取出对应Mask类型的属性值
    |:添加对应的属性值
    & =~与非  或者(^异或):去掉Mask类型的属性值

2、View的测量

    onMeasure方法里面调用setMeasuredDimension()确定当前View的大小

3、ViewGroup的测量

    1、遍历测量Child,可以通过下面三个方法来遍历测量Child
        measureChildWithMargins
        measureChild
        measureChildren

、setMeasuredDimension 确定当前ViewGroup的大小
4、假如去自定义View,ViewGroup,要如何做好Measure?

View自定义控件套路

套路:最终调用setMeasuredDimession方法来保存自己的测量宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
final int specMode = MeasureSpec.getMode(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/* Parent says we can be as big as we want. Just don't be larger
than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
```
#### ViewGroup绘制套路
套路:
1、测量子view的规格大小
measureChildWithMargins
measureChild
measureChildren

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();

    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);

            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
            
            if (lp.width == LayoutParams.MATCH_PARENT) {
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                        getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
                        lp.leftMargin - lp.rightMargin,
                        MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }
            
            if (lp.height == LayoutParams.MATCH_PARENT) {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                        getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
                        lp.topMargin - lp.bottomMargin,
                        MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
1
2
3
4
2、通过子view的规格大小来确定自己的大小 setMeasuredDimession


ViewGroup layout套路

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom,
                              boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }


            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
1
2
			
最后附上经典的```FlowLayout```

public class FlowLayout extends ViewGroup {

private int mGravity = (isIcs() ? Gravity.START : Gravity.LEFT) | Gravity.TOP;

private final List<List<View>> mLines = new ArrayList<List<View>>();
private final List<Integer> mLineHeights = new ArrayList<Integer>();
private final List<Integer> mLineMargins = new ArrayList<Integer>();

public FlowLayout(Context context) {
    this(context, null);
}

public FlowLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.FlowLayout, defStyle, 0);

    try {
        int index = a.getInt(R.styleable.FlowLayout_android_gravity, -1);
        if (index > 0) {
            setGravity(index);
        }
    } finally {
        a.recycle();
    }
    init();
}

private void init() {
    invalidate();
}

/**
 * {@inheritDoc}
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
    int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

    int width = 0;
    int height = getPaddingTop() + getPaddingBottom();

    int lineWidth = 0;
    int lineHeight = 0;

    int childCount = getChildCount();

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

        View child = getChildAt(i);
        boolean lastChild = i == childCount - 1;

        if (child.getVisibility() == View.GONE) {

            if (lastChild) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }

            continue;
        }

        measureChildWithMargins(child, widthMeasureSpec, lineWidth, heightMeasureSpec, height);

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

        int childWidthMode = MeasureSpec.AT_MOST;
        int childWidthSize = sizeWidth;

        int childHeightMode = MeasureSpec.AT_MOST;
        int childHeightSize = sizeHeight;

        if (lp.width == LayoutParams.MATCH_PARENT) {
            childWidthMode = MeasureSpec.EXACTLY;
            childWidthSize -= lp.leftMargin + lp.rightMargin;
        } else if (lp.width >= 0) {
            childWidthMode = MeasureSpec.EXACTLY;
            childWidthSize = lp.width;
        }

        if (lp.height >= 0) {
            childHeightMode = MeasureSpec.EXACTLY;
            childHeightSize = lp.height;
        } else if (modeHeight == MeasureSpec.UNSPECIFIED) {
            childHeightMode = MeasureSpec.UNSPECIFIED;
            childHeightSize = 0;
        }

        child.measure(
                MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
                MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode)
        );

        int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

        if (lineWidth + childWidth > sizeWidth) {

            width = Math.max(width, lineWidth);
            lineWidth = childWidth;

            height += lineHeight;
            lineHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

        } else {
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        }

        if (lastChild) {
            width = Math.max(width, lineWidth);
            height += lineHeight;
        }

    }

    width += getPaddingLeft() + getPaddingRight();

    setMeasuredDimension(
            (modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width,
            (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height);
}

/**
 * {@inheritDoc}
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

    mLines.clear();
    mLineHeights.clear();
    mLineMargins.clear();

    int width = getWidth();
    int height = getHeight();

    int linesSum = getPaddingTop();

    int lineWidth = 0;
    int lineHeight = 0;
    List<View> lineViews = new ArrayList<View>();

    float horizontalGravityFactor;
    switch ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
        case Gravity.LEFT:
        default:
            horizontalGravityFactor = 0;
            break;
        case Gravity.CENTER_HORIZONTAL:
            horizontalGravityFactor = .5f;
            break;
        case Gravity.RIGHT:
            horizontalGravityFactor = 1;
            break;
    }

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

        View child = getChildAt(i);

        if (child.getVisibility() == View.GONE) {
            continue;
        }

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

        int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
        int childHeight = child.getMeasuredHeight() + lp.bottomMargin + lp.topMargin;

        if (lineWidth + childWidth > width) {
            mLineHeights.add(lineHeight);
            mLines.add(lineViews);
            mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft());

            linesSum += lineHeight;

            lineHeight = 0;
            lineWidth = 0;
            lineViews = new ArrayList<View>();
        }

        lineWidth += childWidth;
        lineHeight = Math.max(lineHeight, childHeight);
        lineViews.add(child);
    }

    mLineHeights.add(lineHeight);
    mLines.add(lineViews);
    mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft());

    linesSum += lineHeight;

    int verticalGravityMargin = 0;
    switch ((mGravity & Gravity.VERTICAL_GRAVITY_MASK)) {
        case Gravity.TOP:
        default:
            break;
        case Gravity.CENTER_VERTICAL:
            verticalGravityMargin = (height - linesSum) / 2;
            break;
        case Gravity.BOTTOM:
            verticalGravityMargin = height - linesSum;
            break;
    }

    int numLines = mLines.size();

    int left;
    int top = getPaddingTop();

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

        lineHeight = mLineHeights.get(i);
        lineViews = mLines.get(i);
        left = mLineMargins.get(i);

        int children = lineViews.size();

        for (int j = 0; j < children; j++) {

            View child = lineViews.get(j);

            if (child.getVisibility() == View.GONE) {
                continue;
            }

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

            // if height is match_parent we need to remeasure child to line height
            if (lp.height == LayoutParams.MATCH_PARENT) {
                int childWidthMode = MeasureSpec.AT_MOST;
                int childWidthSize = lineWidth;

                if (lp.width == LayoutParams.MATCH_PARENT) {
                    childWidthMode = MeasureSpec.EXACTLY;
                } else if (lp.width >= 0) {
                    childWidthMode = MeasureSpec.EXACTLY;
                    childWidthSize = lp.width;
                }

                child.measure(
                        MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode),
                        MeasureSpec.makeMeasureSpec(lineHeight - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY)
                );
            }

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            int gravityMargin = 0;

            if (Gravity.isVertical(lp.gravity)) {
                switch (lp.gravity) {
                    case Gravity.TOP:
                    default:
                        break;
                    case Gravity.CENTER_VERTICAL:
                    case Gravity.CENTER:
                        gravityMargin = (lineHeight - childHeight - lp.topMargin - lp.bottomMargin) / 2;
                        break;
                    case Gravity.BOTTOM:
                        gravityMargin = lineHeight - childHeight - lp.topMargin - lp.bottomMargin;
                        break;
                }
            }

            child.layout(left + lp.leftMargin,
                    top + lp.topMargin + gravityMargin + verticalGravityMargin,
                    left + childWidth + lp.leftMargin,
                    top + childHeight + lp.topMargin + gravityMargin + verticalGravityMargin);

            left += childWidth + lp.leftMargin + lp.rightMargin;

        }

        top += lineHeight;
    }

}

@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return new LayoutParams(p);
}

/**
 * {@inheritDoc}
 */
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}

/**
 * {@inheritDoc}
 */
@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return super.checkLayoutParams(p) && p instanceof LayoutParams;
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void setGravity(int gravity) {
    if (mGravity != gravity) {
        if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
            gravity |= isIcs() ? Gravity.START : Gravity.LEFT;
        }

        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
            gravity |= Gravity.TOP;
        }

        mGravity = gravity;
        requestLayout();
    }
}

public int getGravity() {
    return mGravity;
}

/**
 * @return <code>true</code> if device is running ICS or grater version of Android.
 */
private static boolean isIcs() {
    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
}

public static class LayoutParams extends MarginLayoutParams {

    public int gravity = -1;

    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);

        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout);

        try {
            gravity = a.getInt(R.styleable.FlowLayout_Layout_android_layout_gravity, -1);
        } finally {
            a.recycle();
        }
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    public LayoutParams(ViewGroup.LayoutParams source) {
        super(source);
    }

}

}