View的绘制
2017-09-21 13:48
120 查看
摘要
本文先描述Android的视图结构(ContentView和DecorView);然后描述MeasureSpec和LayoutParams的对应关系;最后描述view的measure、layout、draw过程。ContentView和DecorView
DecorView是PhoneWindow的内部类private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
DecorView的结构
DecorView的具体样式结构和Android版本、厂家定制有关。大体是下面这样的:我们在Activity中,通过setContentView()来设置ContentView。
/** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */ public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
MeasureSpec
从MeasureSpec的类注释看:MeasureSpec由ViewGroup传给子View,里面包含了width和height的信息。MesureSpec由SpecSize和SpecMode组成。SpecMode包含三种:
- UNSPECIFIED
Parent不限制View的大小,想多大就多大
- EXACTLY
Parent规定了View的尺寸,不论View设置多大,只能是规定的尺寸大小。
- AT_MOST
View不能大于Parent指定的大小。
MeasureSpec和LayoutParams的对应关系
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; // Parent has imposed a maximum size on us 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 = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
这段代码简单明了,可由下图表示,图片来自《Android开发艺术探索》插图
View的绘制流程
总览
大致源码
performTraversals
ViewRootImpl.java中的performTraversals方法。其代码非常长,其中依次调用了performMeasure、performLayout、performDraw。private void performTraversals() { ... if (!mStopped) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 1 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } } if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... } if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } } ... }
measure过程
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
performMesure,里面逻辑很简单,此处mView就是DecorView,那么就从DecorView开始Measure。
DecorView继承自FramLayout,DecorView和FramLayout均没有measure方法,那么就是View的Measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { }
这个方法不看了,看注释吧,注释写得很到位:
它会调用onMeasure
* The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
到FramLayout的onMeasure中
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidthMeasureSpec; int childHeightMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } if (lp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }
它会遍历Child,调用measureChildWithMargins,measureChildWithMargins会调用child.measure。
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 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); }
至此, DecorView.performMeasure->measure->onMeasure->child.measure的主流程从代码层面得到了验证。
layout过程
ViewRootImpl.performLayoutprivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) { …… host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); …… }
到Decor的Layout方法中,View.layout
public void layout(int l, int t, int r, int b) { …… if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; } …… }
FramLayout.onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); mForegroundBoundsChanged = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { …… child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
对可见属性不是GONE的child进行layout。
至此,DecorView.performLayout->layout->onLayout->child.layout过程也从代码层面得到了验证。
draw过程
private void performDraw() { if (!mAttachInfo.mScreenOn && !mReportNextDraw) { return; } final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mReportNextDraw) { mReportNextDraw = false; if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } try { mWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } } }
DecorView.draw:
public void draw(Canvas canvas) { super.draw(canvas); if (mMenuBackground != null< c62c /span>) { mMenuBackground.draw(canvas); } }
FramLayout的draw中会调View的Draw。
View的Draw也贼长,但是借助注释可以很快知道其过程:
// 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
它会调到onDraw中,然后dispatchDraw来完成children的draw。
和之前的measure/Layout过程不同的是,draw并不在onDraw中进行子View的Draw,而是通过dispatchDraw。onDraw只负责其本身的绘制。
至此,draw过程也从代码层面上得到了验证。
相关文章推荐
- Android中View的绘制流程以及View的优化
- 【View层】IOS纯代码绘制界面
- View绘制4-onDraw
- Android 之 TextView内部如何绘制Span样式
- Android视图绘制流程完全解析,带你一步步深入了解View(四)
- 自定义View学习-绘制一个简单的圆
- 在SurfaceView中绘制文本时 无法自动换行问题的解决办法
- 从源码上剖析Android View绘制Drawable的原理
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android view的绘制流程(三)
- View 绘制流程
- Android自定义View绘制真正的居中文本
- Android View绘制流程简析
- view的绘制过程
- 关于android ui的优化 view 的绘制速度[转]
- Android自定义View --- 绘制圆环
- View的绘制、事件传递过程
- android初级学习之深入了解View绘制流程(一)
- Android中View绘制流程以及invalidate()等相关方法分析
- 自定义View绘制图形一(绘制静态图形)