measure过程
onMeasure()
官方翻译:
测量视图及其内容,以确定测量宽度和测量高度。这个方法是由measure(int,int)调用的,它应该被子类覆盖,以提供准确和有效的内容度量。当重写该方法时,你必须调用setMeasuredDimension(int, int)来存储该视图的测量宽度和高度。会由measure(int,int)抛出IllegalStateException。对背景大小的默认值的基本类实现,除非测量值允许较大的尺寸。子类应该覆盖onMeasure(int,int),以便更好地度量的内容。如果覆盖该方法,它是子类的责任确保测量高度和宽度至少视图的最小高度和宽度getSuggestedMinimumHeight()和getSuggestedMinimumWidth()。
measure(int,int)方法时final的所以子类不能重写
如果需要支持wrap_content属性,必须重写onMeasure()方法
源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//来到getDefaultSize方法
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:
//UNSPECIFIED模式返回第一个值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//EXACTLY模式返回指定值
result = specSize;
break;
}
return result;
}
//获取模式
public static int getMode(int measureSpec) {
//MODE_MASK:位运算0x11左移30位 也就是11后面30个0
//再按位与获取头两位为模式
return (measureSpec & MODE_MASK);
}
//获取大小
public static int getSize(int measureSpec) {
//再按位与获取头两位为大小
return (measureSpec & ~MODE_MASK);
}
测量模式
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: 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.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
UNSPECIFIED
任意大小,要多大就有多大,一般用于系统内部的测量过程,在这种情况下View的大小为getDefaultSize第一个参数,宽/高分别为getSuggestedMinimumWidth(),getSuggestedMinimumHeight()返回值
protected int getSuggestedMinimumWidth() {
//没有设置背景就为android:minWidth属性指定值否则为0
//如果有背景那么为就是mBackground(Drawable)的getMinimumWidth()返回值
return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
EXACTLY
精确值模式,比如 android:layout_width="100dp",或者指定为match_parent属性,使用的是EXACTLY模式,View类默认的onMeasure()方法只支持EXACTLY模式。
AT_MOST
最大值模式,父容器指定了一个大小,View的大小不能大于这个值,在布局中使用wrap_content相当于match_parent时候的模式就是AT_MOST,此时的specSize就是parentSize,而parentSize是父容器中目前可以使用的大小,所以就相当于使用了match_parent。
解决办法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
//设置默认的宽高
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
//设置默认的宽度
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
//设置默认的高度
setMeasuredDimension(widthSpecSize, mHeight);
}
}
layout过程
layout的作用是ViewGroup用来确定子元素的位置的,layout方法是确定View本身,当ViewGroup的位置被确定了,onLayout会遍历所有子元素执行layout方法。
layout()
/**
* Assign a size and position to a view and all of its
* descendants
*
* <p>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().</p>
*
* <p>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.</p>
*
* @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
*/
@SuppressWarnings({"unchecked"})
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;
}
//setFrame()中设定的值
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);
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<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)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);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
大致流程: 先通过setFrame方法设定View的四个顶点初始化四个值接着调用onLayout(),可以查看一下LinearLayout的onLayout()方法。
getMeasureWidth和getWidth方法区别
/**
* 测量宽/高形成与View的measure过程
*/
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
/**
* 最终宽/高形成与View的layout过程
*/
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
一般认为这俩个方法返回的值是相等的,除非特殊情况,重写layout方法重新赋值就不一定了
draw过程:
绘制流程其实是一套模板设计模式
源码
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
//决定onDraw()方法是否调用,是否是不透明
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background 对View的背景进行绘制
* 2. If necessary, save the canvas' layers to prepare for fading 保存当前的图层信息(可跳过)
* 3. Draw view's content 绘制View的内容
* 4. Draw children 对View的子View进行绘制(如果有子View)
* 5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果(可跳过)
* 6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条)
*
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
//绘制背景
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
//如果ViewGroup没有指定背景色不会调用此方法,绘制自己
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(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);
// we're done...
return;
}
.....
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
....
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
....
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
由此看来执行过程:drawBackground(canvas) --> onDraw() --> dispatchDraw(canvas)
--> onDrawForeground(canvas)
ViewGroup是继承View的抽象类,它的dispatchDraw(canvas)在View中的draw()方法中直接调用,ViewGroup的dispatchDraw()方法由dirtyOpaque决定是否调用,也就是child是可见或者上面有动画,那么就一定会调用child的draw()。
View中的特殊方法setWillNotDraw
一个View如果不需要绘制任何内容就可以这个标志位为true系统会进行优化,当我们的自定义控件继承与ViewGroup并且不需要绘制任何功能,就可以开启这个标志位,当需要onDraw来绘制内容需要显示关闭这个标志位。
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
网友评论