View绘制学习(一)

View绘制学习之onMeasure()

1 setContentView源码学习

首先,onCreate调用setContentView(int resId),调用AppCompatActivity.java中代码:

@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

getDelegate()最终返回一个AppCompatDelegateImpl,它是一个AppCompatDelegate的实现类对象,所以setContentView实际调用的是AppCompatDelegate#setContentView(int resId)接口,实现在Activity#setContentView(int)

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
public Window getWindow() {
    return mWindow;
}

打开Window发现是一个抽象类,这里看到它的注释:

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */

打开PhoneWindow.java(要先把android.jar加入libs文件夹中,右键Add as Library才能打开)

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

如果mContentParent为首次加载,则执行installDecor(),否则清空后再调用inflate方法加载视图,相关方法关键代码如下:

private void installDecor() {
	if (mDecor == null) {
        mDecor = generateDecor(-1);
    }
    if (mContentParent == null) {
    	mContentParent = generateLayout(mDecor);
    }
}
protected DecorView generateDecor(int featureId) {
	return new DecorView(context, featureId, this, getAttributes());
}
public class DecorView extends FrameLayout...

总结setContentView加载过程如下:
在这里插入图片描述

2 MeasureSpec类

看一下这个类的介绍

/**
 * A MeasureSpec encapsulates the layout requirements passed from parent to child.
 * Each MeasureSpec represents a requirement for either the width or the height.
 * A MeasureSpec is comprised of a size and a mode. There are three possible
 * modes:
 * <dl>
 * <dt>UNSPECIFIED</dt>
 * <dd>
 * The parent has not imposed any constraint on the child. It can be whatever size
 * it wants.
 * </dd>
 *  * <dt>EXACTLY</dt>
 * <dd>
 * The parent has determined an exact size for the child. The child is going to be
 * given those bounds regardless of how big it wants to be.
 * </dd>
 *  * <dt>AT_MOST</dt>
 * <dd>
 * The child can be as large as it wants up to the specified size.
 * </dd>
 * </dl>
 *  * MeasureSpecs are implemented as ints to reduce object allocation. This class
 * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
 */

它是View的一个内部类,封装了父类传递给子类的测量要求,是一个32位的int类型,前两位为mode,后30位为sizemode+size即为一个MeasureSpec对象。
三种测量模式:

  • UNSPECIFIED:父类对子类的大小没有要求,一般是在特殊情况下出现,如在父布局是ScrollView中才会出现这种测量模式
  • EXACTLY:父控件已经确切的指定了子View的大小,当宽或高设为确定值时:例如width=20dpheight=30dp,或者为match_parent。它会使用MeasureSpec.EXACTLY测量模式
  • AT_MOST:子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小,当宽或高设为wrap_content时,它会使用MeasureSpec.AT_MOST测量模式

3 onMeasure()与View的绘制流程

View#onMeasure()如下:

 /**
  * <p>
  * If this method is overridden, it is the subclass's responsibility to make
  * sure the measured height and width are at least the view's minimum height
  * and width ({@link #getSuggestedMinimumHeight()} and
  * {@link #getSuggestedMinimumWidth()}).
  * </p>
  *
  * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
  *                         The requirements are encoded with
  *                         {@link android.view.View.MeasureSpec}.
  * @param heightMeasureSpec vertical space requirements as imposed by the parent.
  *                         The requirements are encoded with
  *                         {@link android.view.View.MeasureSpec}.
  *
  */
 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

由注释可知两个参数由父View传递而来,从View绘制开始的关键代码进行分析

private void performTraversals() {
	WindowManager.LayoutParams lp = mWindowAttributes;
	...
	int desiredWindowWidth;
	int desiredWindowHeight;
	...
	if (shouldUseDisplaySize(lp)) {
        // NOTE -- system code, won't try to do compat mode.
        Point size = new Point();
        mDisplay.getRealSize(size);
        desiredWindowWidth = size.x;
        desiredWindowHeight = size.y;
    } else {
        desiredWindowWidth = mWinFrame.width();
        desiredWindowHeight = mWinFrame.height();
    }
    ...
	int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
	int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
	...
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

可以看到在performMeasure方法中调用了View#measure()

public class View implements...{
	public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	...
	onMeasure(widthMeasureSpec, heightMeasureSpec);
	...
	}
}

此时回到了onMeasure()方法,下面看此默认方法中的实现

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
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;
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;

        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

参考文章

[Android][MeasureSpec的三个测量模式]
Android开发之自定义控件(一)—onMeasure详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值