Android Measure探索

《Android内核剖析》,《Android开发艺术探索》

  • MeasureSpec介绍
    • 将SpecMode与SpecSize打包得到int值,MeasureSpec也提供了解包方法来分别获取SpecMode与SpecSize.
    • SpecSize:视图大小值
    • SpecMode: 测量模式
    1. EXACTLY:确定的,父视图希望子视图大小应该是MeasureSpec中SpecSize值.
    2. AT_MOST:最多,子视图大小最多是MeasureSpec中SpecSize值.
    3. UNSPECIFIED:没有限制,子视图可以根据自身特性设置大小.
// View.MeasureSpec.java
public static class MeasureSpec {
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    // 打包获得int值
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    // 解包获取SpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    // 解包获取SpecSize
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}
  • DecorView如何measure.
  1. 获取父容器的MeasureSpec
// ViewRootImpl.java
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    // WindowManager.LayoutParams默认宽高是MATCH_PARENT.
    final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
    public ViewRootImpl(Context context, Display display) {
        // 第一次添加视图时候为true
        mFirst = true;
    }
    private void performTraversals() {
        WindowManager.LayoutParams lp = mWindowAttributes;
        // 如果是第一次添加视图
        if (mFirst) {
            ...
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // 这里测量状态栏这些尺寸
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                // 这里就是测量屏幕尺寸了
                DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
            ...
        } else {
            ...
        }
        //执行布局
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    }
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
        measureHierarchy(host, lp, mView.getContext().getResources(),desiredWindowWidth, desiredWindowHeight);
    }
    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        // 这里开始创建屏幕的MeasureSpec,给DecorView测量用.
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    // 调用DecorView父类的measure()
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ...
        try {
            // 对DecorView进行测量
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
        }
    }

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        // rootDimension 是MATCH_PARENT.
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // 传入DecorView中的MeasureSpec是这个
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
}
  1. 调用DecorView.measure()
// DecorView父类是FrameLayout,不存在measure()方法.
// FrameLayout父类是ViewGroup,不存在measure()方法.
// 只有View中存在measure()方法.
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 当调用DecorView.measure(),最先走View.measure().
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        // DecorView中重写了,所以会走 DecorView.onMeasure()方法.
        onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    // 但是最终会走这里,为DecorView设置真实的尺寸
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    // 返回View建议使用的最小尺寸
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    // size代表此时DecorView建议最小尺寸
    // measureSpec代表屏幕对DecorView测量模式和建议尺寸.
    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:// 这是ViewRootImpl为DecorView创建的测量模式
            result = specSize;// 所以DecorView最终尺寸使用的是ViewRootImpl提供的建议尺寸,到这里DecorView的测量就完成了.
            break;
        }
        return result;
    }

}

// PhoneWindow.DecorView.java
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        // 走到这里之后最终会调用View.onMeasure()
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
}
  • View如何measure.
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 接收到父控件传递过来的MeasureSpec
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ..
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) {
            ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                ...
                // 看这里
                onMeasure(widthMeasureSpec, heightMeasureSpec);
            } else {
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            }
        }
        ...    
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 设置尺寸
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    // 设置View大小
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        ...
    }
    // 根据不同的measureSpec以及期望大小返回不同的值.
    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;
    }
    
}
  • ViewGroup中默认measure.
  1. ViewGroup中含有很多子View,而View.measure()也是在ViewGruop中调用的.先来看下ViewGroup中如何确定传递给子View的measureSpec.
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    // spec:父控件传递给ViewGroup的MeasureSpec
    // padding: ViewGroup已经占用的控件尺寸
    // childDimension:子控件想要的尺寸,一般写在xml中
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);// 测量模式
        int specSize = MeasureSpec.getSize(spec);// 测量值
        int size = Math.max(0, specSize - padding);
        int resultSize = 0;
        int resultMode = 0;
    
        switch (specMode) {
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        // 根据不同的测量模式测量值还有子View想要的值得到一个子View的MeasureSpec.
        // 子View的MeasureSpec获取有规律,可以用一个图表示.
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
}

ViewGruop中获取创建子View的MeasureSpec规则
MeasureSpec规则

  1. ViewGroup默认测量子View的方法
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    // 默认测量子View的方法
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            // 获取子View
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                // 测量子View
                // widthMeasureSpec,heightMeasureSpec:ViewGroup中的MeasureSpec
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    // 测量子View
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        // 获取子View布局参数
        final LayoutParams lp = child.getLayoutParams();
        // 用这些条件去获取子View的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);
        // 调用子View.measure();
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值