先记住测量的起点是View的measure()方法,然后在measure()方法调用onMeasure()方法来自己测量。 所以主要牵涉的二个方法,measure()和onMeasure()。

我们先看一下FrameLayout代码。假设FrameLayout要求被自我测量,那么它就会调用onMeasure()。

//FrameLayout.class
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ......
//获取子View,告诉他们“你们要调用自己测量了。调用方法measureChildWithMargins()”
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
//等到所有的子View测量完毕,就进行设置自己的大小;如果有需要也可能会进行二次测量
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
//ViewGroup.class
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        ......
//父亲让子类测量时,调用的就是View的measure()。这里传的两个参数就是父类安排子类的大小
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//View.class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (forceLayout || needsLayout) {
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
......
//子类进行自我测量中...
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {       

这里的FrameLayout是继承了ViewGroup。

  • View中的onMeasure()主要是用来计算自己的尺寸。
  • ViewGroup中的onMeasure()主要是用来调用子类的measure()进行自我测量。同时计算自己的尺寸。
  • 在onMeasure()自己计算尺寸的时候一定要注意是是需要要符合父类的限制的,这个限制就是onMeasure()方法中传入的widthMeasureSpec和heightMeasureSpec。这两个参数中存储着两个信息,大小和限制的类型。

    获取大小和限制类型的方式

    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    

    限制的类型一个是三个

    MeasureSpec.AT_MOST:老爸告诉你,你只能这么大 MeasureSpec.EXACTLY:老爸告诉你,你就这么大,不能讨教还价 MeasureSpec.UNSPECIFIED:老爸告诉你,你想这么着就怎么着

    如何设置当前View的尺寸

    一般计算完当前View的尺寸后可以通过调用resolveSize()或resolveSizeAndState()方法计算出当前的大小用setMeasuredDimension

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
            final int specMode = MeasureSpec.getMode(measureSpec);
            final int specSize = MeasureSpec.getSize(measureSpec);
            final int result;
            switch (specMode) {
                case MeasureSpec.AT_MOST:
                    if (specSize < size) {
                        result = specSize | MEASURED_STATE_TOO_SMALL;
                    } else {
                        result = size;
                    break;
                case MeasureSpec.EXACTLY:
                    result = specSize;
                    break;
                case MeasureSpec.UNSPECIFIED:
                default:
                    result = size;
            return result | (childMeasuredState & MEASURED_STATE_MASK);
    

    来,我们举几个例子

    1.如果我希望ImageView一直是方

    public class SquareImageView extends ImageView {
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //先测量出大小
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //然后根据测量的结果重新设置大小
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            if (width > height)
                setMeasuredDimension(width,width);
                setMeasuredDimension(height,height);
    

    第一步,在调用super.onMeasure()方法后其实已经测量出当前View的大小。 第二步,为了使ImageView变成方的又进行了高宽。 第三步,调用setMeasuredDimension(),重新设置了测量的大小。

    2.如果就是想自己计算

    public class CalculationView extends View {
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //去掉super.onMeasure()方法
            //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //通过一顿计算得出当前View的大小
            int width = ......
            int height = ......
            width = resolveSize(width,widthMeasureSpec);
            height = resolveSize(height,heightMeasureSpec);
            setMeasuredDimension(width,height);
    

    如果是纯粹的自己重新计算大小,就不需要调用super.onMeasure()。 第一步,计算自己应该有的大小。 第二步,用resolveSize方法结合父类的限制获取大小。 第三步,调用setMeasuredDimension()设置大小。

    3.如果是对ViewGroup进行计算一般的流程

    public class CalculationView extends ViewGroup {
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int count = getChildCount();
            for (int i = 0; i < count; i++){
                View view  = getChildAt(i);
                LayoutParams lp =  view.getLayoutParams();
                int widthMode = MeasureSpec.getMode(widthMeasureSpec);
                int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                int heightMode = MeasureSpec.getMode(heightMeasureSpec);
                int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                 * 进行一顿就算操作得出各种结论
                 * 比如,最宽的是多宽,最高的是多高等信息
             * 设置当前View的大小
            setMeasuredDimension(measuredWidth,measuredHeight);
            for (int i=0;i<count;i++) {
                 * 然后通过自己的widthMode,widthSize,heightMode,heightSize和lp进行各种计算
                 * 比如最终确定高是height,宽是width。
                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);
                int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
                view.measure(childWidthMeasureSpec,childHeightMeasureSpec);
    

    在继承ViewGroup的类中比继承View的类多一个让子类调用measure()的步骤 第一步,获取子类的大小,结合自己布局的规则进行测量和保存必要的数据。 第二步,通过第一步的计算已经知道了自己的大小,用setMeasuredDimension()方法进行设置。 第三步,对各个子类进行限制并且调用view.measure()方法。

    黄金分割线

    就上面所有的内容只是测量,仅仅是测量,还没有到真正布局的步骤。 黄金分割线下就是布局了。

    先记住布局的起点是View的layout()方法,然后layout()方法中会调用onLayout()方法。

    我们先看看FrameLayout代码,假设要执行布局就会调用onLayout()方法。

    //FrameLayout.class
    @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();
    ......
             //对各子View进行布局操作
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
    
    //View.class
    public void layout(int l, int t, int r, int b) {
    //设置布局的位置和大小
        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.class
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
    //设置View的位置大小
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
        return changed;
    

    布局的逻辑其实很简单,就是递归调用onLayout()和layout()方法 第一步,在onLayout方法中进行必要的计算,主要是针对当前布局。 第二步,获取每一个View的LayoutParams的设置参数和大小,一顿计算和操作。 第三步,调用子View的layout()方法,调用对View位置起作用的就是setFrame()方法 第四步,继续调用子类的onLayout()方法。

    在View的onLayout()方法是空的,ViewGroup的onLayout是一个抽象方法。

  • 测量涉及measure()和onMeasure()方法。
  • measure()主要的作用就是优化和调用onMeasure()方法。
  • 重要的是onMeasure()方法。根据不同需求在方法中一顿计算、测量、设置测量后的大小并且可能会调用子类的measure()方法。
  • 记住MeasureSpec类和里面的方法、参数。记住View.resolveSize()。
  • 布局涉及layout()和onLayout()方法。
  • layout()主要作用就是设置当前View的位置和调用onLayout()方法。
  • 重要的是onLayout()方法。根据不同布局需求利用子类的LayoutParams参数和大小在方法中一顿计算,然后调用子类的layout()方法。
  • 我叫陆大旭。

    一个懂点心理学的无聊程序员大叔。 看完文章无论有没有收获,记得打赏、关注和点赞!

    分类:
    阅读
    标签: