主要记录学习《Android开发艺术探索》
1.
View的绘制过程:
ActivityThread(handlerResumeActivity())--->WindwowManagerImpl(addView())--->WindowManageGlobal(addView())--->ViewRootImpl(requestLayout())--->ViewRootImpl(scheduleTraversals())--->ViewRootImpl(doTraversal())--->ViewRootImpl(preformTraversals());
ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView 添加到
Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
root=new ViewRootImpl(view.getContext(),display);
root.setView(view,panelParentView);
View的绘制流程从ViewRootImpl的 performTraversals()开始
依次调用以下三个方法
1.performMeasure()
此方法调用View的measure()的方法--->调用View的onMeasure()方法
2.performLayout()
此方法调用View的layout()的方法--->
调用setFrame(l, t, r, b)确定自身在父控件的位置
调用View的onLayout()方法(ViewGroup)会确定子控件在自身位置
3.performDraw()
Measure过程结束后可通过getMeasuredWidth和getMeasuredHeight方法获取View的测量宽高。
Layout过程结束后可通过getTop,getBottom,getLeft,getRight来拿到View的四个点的坐标,并可通过getWidth和getHeight方法获取到View的最终宽高。
Draw过程结束后View的内容才最终显示在屏幕上。
DecorView是一个FrameLayout 内部一般包含一个LinearLayout,这个LinearLayout里面有上下两个部分(具体情况和Android版本和主题有关)上面是标题栏,下面是内容栏(内容栏为FrameLayout),内容栏的id为content。
MeasureSpec 是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode 指测量模式共有三类
1.UNSPECIFIED:表示父容器对View不做任何限制,要多大给多大。
2.EXACTLY:表示父容器已检测出View的所需确切大小,这时候View的最终大小就是SpecSize所指定的值。对应LayoutParams中match_parent和具体的数值这两种模式
3.AT_MOST:指定了一个可用大小的即SpecSize,View的大小不能大于这个值。具体要看不同的View的具体实现。它对应于LayoutParams中的wrap_content.
DecorView的MeasureSpec有屏幕尺寸和自身的LayoutParams共同决定。
普通的View的MeasureSpec需要父容器和自身的LayoutParams一起来决定。
View的measure过程是由ViewGroup测量过程传递过来的。
getChildMeasureSpec()
测量规则如下:
parentSize为父控件去除padding的可使用大小。
1.若View指定了确切的尺寸childSize。View的MeasureSpec就是(EXACTLY,childSize)
2.若View是match_parent的
2.1父容器的SpecMode为EXACTLY 。View的MeasureSpec就是(EXACTLY, parentSize)
2.2父容器的SpecMode为AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)
3.若View是wrap_content的
3.1父容器的SpecMode为EXACTLY 。View的MeasureSpec就是(AT_MOST, parentSize)
3.2父容器的SpecMode为AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)
2
View的工作流程
1)measure过程:
1.View的measure过程
View的measure方法是final无法重写,但是View的measure方法会调用View的onMeasure的方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
// AT_MOST和EXACTLY两种模式没有区别大小都为父控件可用大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// 若没有设置背景就为mMinWidth,若设置了背景就为背景大小和mMinWidth的中最大值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public int getMinimumHeight() { return mMinHeight;}
从getDefaultSize方法的实现来看,直接继承View的自定义控件需要重写onMesasure的方法。否则的在布局中使用wrap_content相当于使用match_parent。
private int mWidth;
private int mHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
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);
}
}
我们只需要给View设定一个默认的内部宽高(mWidth和mHeight)并在wrap_content时进行设置此宽高即可,至于如何设定,可根据具体情况设置
2.ViewGroup的measure过程
ViewGroup除了测量自身外还会遍历去调用所有子元素的measure方法,各个子元素再去递归去执行这个过程。ViewGroup是一个抽象类并没有重写onMeasure方法,但它提供了一个measureChildren的方法
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);
}
}
}
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并没有定义测量的具体过程,ViewGroup是一个抽象类,其测量过程需要各个子类自己实现。不做统一实现是因为不同的ViewGroup子类具有不同的布局特性,测量细节各不相同。
获取view的测量宽高
1.在onWindowFocusChanged方法中View已经初始化完毕,当Activity的窗口失去焦点或得到焦点的时候均会被调用
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
}
}
- post方法可以把一个Runnable投递到消息队列的尾部,Looper取出消息调用Runnable的时候View已经初始化好了。
view.post(new Runnable() {
@Override
public void run() {
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
Log.d(TAG, "run: " +width);
}
});
3.ViewTreeObserver当View树的状态发生改变或者View树内部的View的可见性发生改变是 onGlobalLayout方法会回调。
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
}
});
2)layout过程:
layout方法确定自身在父控件的中位置,若为ViewGroup要重新来onLayout确定子控件的位置。
layout方法通过调用setFrame确定mLeft,mTop,mBottom,mRight这四个值,四个顶点确定,那么View自身在父控件的位置就确定了。
layout在View和ViewGroup一般不用重新。
但自定义ViewGroup的时候要重写onLayout方法 来确定子View的摆放位置,onLayout一般也是遍历子View并调用子View的layout方法来确定摆放位置。
3)draw过程
绘制背景 drawBackground(canvas);
绘制自己 onDraw(canvas);
绘制children dispatchDraw(canvas);
绘制装饰 onDrawForeground(canvas);
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;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 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;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// 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);
}
设置不绘制自身,View默认关闭,ViewGroup默认开启。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}