- DecorView 是界面上的顶层view. ViewRoot是连接WindowManager和DecorView的纽带.view的绘制流程从ViewRoot的perfromTraversals开始.
- ViewRoot作为顶级View,通常包括一个竖直方向的LinerLayout.这个LinerLayout分两部分.上边是标题栏.就是屏幕显示时间的那一行,下边是内容栏.id为content,我们平时设置的setContentView(),就是在想contentView里添加子类.通过ViewGroup content =(ViewGroup)findViewById(android.R.id.content);拿到contentView. content.getChildAt(0);拿到我们设置的View;
- MeasureSpace 用一个32位的int代表测量的数据和类型,高二位代表SpecMode,表示测量模式,低30位 SpecSize表示测量大小.
- MeasureSpace的三种模式
- UNSPECIFIED:父容器不做限制,通常用于系统内部.
- EXACTLY 指定大小,view此时的大小就是SpecSize的值.它对应于LayoutParams中的march_parent和具体指定的值(多少dp,sp).
- At_MOST 父容器指定一个可用大小即SpecSize,View不能大于这个值.对于wrap_content.
- MeasureSpace 决定view的宽高,但MeasureSpace由父类和view自己的layoutParams共同决定.
- DecorView的MeasureSpace决定方式
2.png
-接着看vp如何测设计子类的MeasureSpace
//parentWidthMeasureSpec,parentHeightMeasureSpec 是vp自己的measureSpace宽高值
//widthUsed,heightUsed 是被vp额外用掉的宽高值
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//viewgroup自己的measureSpace,第二个参数是vp所使用的值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//调用子类进行测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//得到子类的MeasureSpace 通过父类的specMode和子类Layoutparams的组合得到子类最终的MeasureSpace
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
vp的MeasureSpec
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//vp自己的尺寸减去自己的padding.margin等 最后得到子类能用的最大尺寸
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY: //vp是精确模式
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST: //父类是最大模式
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED: //父类是不确定模式
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
-最终的匹配结果如下,parentSize值最终父类可用大小,既getChildMeasureSpec中的 size
4.png-measure 过程
-view 的measures过程
view 的measure方法是final,既子类无法重写该方法,在measure 中又调用了onMeasure方法,只需要看onMeasure方法就可以.
//widthMeasureSpec,heightMeasureSpec为父类vp传过来的子类的MeasureSpace 宽高值
//getSuggestedMinimumWidth() 是建议的最小宽度,一般为view背景的大小和属性android:minWidth大小的最大值.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//子类可以重写这个方法来决定view的measure大小.但是必须调用setMeasuredDimension()方法,不然会抛出异常.
setMeasuredDimension()方法会设置view宽高的测量值.
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;
}
//UNSPECIFIED的情况可以忽略.看剩下两种情况.所以 getDefaultSize的取值其实就是vp传过来的measureSpace的specSize的值.
- 经过以上代码我们得出结论:直接继承View的自定义空间要重写onMeasure方法并设置wrap_content时自身大小,不然设置wrap_content和设置march_parent的效果一样.
- 因为view 设置为wrap_content, 对应的MeasureSpec 为SpecMode=AT_MOST,SpecSize=parentSize,在onMeasure中就会把父类可用空间的值设置给view.解决办法如下
-ViewGroup 的measures过程
-
viewgroup 出来测量自己,还要遍历子veiw测量大小.vp通过measureChildren方法来遍历测量子类大小
//Gone的子view不会被测量 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } //这个方法和之前的 measureChildWithMarging 方法差不多,只是少了widthUsed,heighUsed参数 protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
-
viewgroup并没有实现具体测量过程,因为他是一个抽象类,需要子类例如LinerLayout等自行实现测量过程.
-
四种方法拿到view的宽高
- Activity/View#onWindowFocusChanged,这个方法的含义标识窗口的交点变化时回调,此时view已经初始化完毕,宽高已经准备好了.此方法会回调多次
public void onWindowFocusChanged(boolean hasFocus){ super.onWindowFocusChanged(hasFocus); if(hasFocus){ int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); } }
-
view.post(runnable) 发送一个消息到消息队列的尾部,等Lopper调用此消息时,view已经初始化完成
view.post(new Runnable{
@Override public void run(){ int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); }
});
-
ViewTreeObserver#onGlobalLayoutListener,当view树的状态或者view树的子view可见性发生改变时会回调,方法会被回调多次
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addonGlobalLayoutListener(new onGlobalLayoutListener(){publci void onGlobalLayout(){ view.getViewTreeObserver().removeGlobalLayoutListener(this);//先移除监听 int width=view.getMeasuredWidth(); int hei=view.getMeasuredHeigh(); }
});
-
view.measure(int,int);需要根据view 的layoutParam 来区分
march_parent :此状态无法得到结果,因为此时父view的测量宽高也不知道
具体数值 :比如宽高为100int width =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY) int heigh =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY) view.measurd(width,heigh);
wrap_content
int width =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY) int heigh =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY) view.measurd(width,heigh);
View 的最大尺寸为30位二进制,既 2的30次方减1 既(1<<30)-1,在最大化模式下,我们用view理论上的最大值去构造MeasureSpec是合理的.
-layout 过程
-
layout 方法确定view本身的位置,在onLayout方法则会确定所有子元素的位置.
-
viewGroup 在位置被确定后,会在onLayout中便利所有子类比调用其layout方法,在layout方法中onLayout方法又被调用.
-
view 的layout方法中, 通过setFrame(l,t,r,b)来设定view的四个定点的位置.父容器通过onLayout方法来确定子view的位置.
-
view和viewgroup均没实现onLayout方法,需要看具体的layout.
-
view 的layout过程
public void layout(int l, int t, int r, int b) { //如果需要在layout前进行一次measure if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } //之前的位置 左上右下 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; //主要部分,设置view的左上右下位置. boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //通知监听器,界面layout发生变化 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); //空实现 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; }
-vieGroup layout过程
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b); //其实还是调用了父类,最终调用view 的layout
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
-
getwidth getMeasuredWidth 区别
-
getMeasuredWith 形成于measure过程.getWidth 形成于layout过程.
-
一般情况两者是相同的.除非如下情况
public void layout (int l,int t,int r,int b){ super.layout(l,t,r+100,b+100); }
-draw 过程
1、对View的背景进行绘制
2、保存当前的图层信息(可跳过)
3、绘制View的内容
4、对View的子View进行绘制(如果有子View)
5、绘制View的褪色的边缘,类似于阴影效果(可跳过)
6、绘制View的装饰(例如:滚动条)
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// 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
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;
}
...
}
分布讲解
1绘制背景
private void drawBackground(Canvas canvas) {
//mBackground是该View的背景参数,比如背景颜色,没有背景就不绘制
final Drawable background = mBackground;
if (background == null) {
return;
}
//根据View四个布局参数来确定背景的边界 mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
setBackgroundBounds();
...
//获取当前View的mScrollX和mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
3.绘制子View,view中这个方法为空,viewgroup实现了这个方法
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime); //这句是重点
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);//这句是重点
}
}
//省略...
}
drawChild(canvas, transientChild, drawingTime)的实现,调用了子view的绘制方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
- view 的draw(Canvas canvas, ViewGroup parent, long drawingTime)
- 我们主要来看核心部分,首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用draw(canvas)方法,开始正常的绘制,即上面所说的六个步骤,否则利用缓存来显示。
- 这一步也可以归纳为ViewGroup绘制过程,它对子View进行了绘制,而子View又会调用自身的draw方法来绘制自身,这样不断遍历子View及子View的不断对自身的绘制,从而使得View树完成绘制
Skip 6 绘制装饰,指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground: public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); } }
网友评论