Android(安卓)View的绘制流程
在项目的开发的过程中,对于一般的需求我们使用Android提供的原生的空间就可以满足开发需求,但是当我们遇到一些特殊的需求需要我们自定义View的时候,需要开发人员实现测量,布局和绘制等操作,这些都依赖于我们对View绘制流程的理解和掌握
先看下Android的UI管理系统的层级关系:
PhoneWindow是Android系统中最基本的窗口系统,每个Activity会创建一个.PhoneWindow是Activity和View系统交互的接口.DecorView本质上是Framelayout,是Activity中所有View的祖先
目录
1.绘制的整体流程
2.MeasureSpec
3.Measure
4.Layout
5.Draw
1.绘制的整体流程
当一个app启动的时候,会启动一个主Activity,Android系统会根据Activity的布局对他进行绘制.绘制从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View空间负责绘制自己,而Viewgroup还要负责告诉自己的子View开始进行绘制,视图树绘制可以分为三个步骤:分别是测量Measure ,布局 Layout ,绘制 Draw.
关于performTraversals()方法,可以去源码查看,在ViewRootImp.java文件中
核心的三个点:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);performLayout(lp, mWidth, mHeight);performDraw();
2.MeasureSpec
MeasureSpec表示的是一个32位的整型值,他的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize .
MeadureSpec 是View类的一个静态内部类,用来表示改如果测量这个View
核心代码 :
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * 不指定测量模式 */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * 精确测量模式 */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * 最大值测量模式 */ public static final int AT_MOST = 2 << MODE_SHIFT; /** * 根据指定的大小和模式创建一个MeasureSpec */ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /** *微调SpecMeasure的大小 */ static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } }
三种测量模式 :
-
UNSPECIFIED :不指定测量模式,父视图没有限制子视图的大小,子视图可以想要任何尺寸.开发中很少见
-
EXACTLY : 精准的测量模式,当该视图的width和height指定为具体的数值或者是match_parent时候生效,表示父视图已经决定子视图的大小,改模式下View的测量值就是SpecSize的值
-
AT_MOST :最大值模式,宽和高为wrap_content的时候,子视图可以是不超过父视图允许的最大尺寸内的任何尺寸
对DecorView来说,他的MeasureSpec是由窗口尺寸和其自身的LayoutParams共同决定,对于普通的View,他的MeasureSpec由父视图和MeasureSpec和自身的layoutParams来决定
3.Measure
Measure方法是用来计算View的实际大小的,通过前面的分析,我们知道页面的绘制流程是从performMeasure开始的
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); } }
具体的测量操作是分发给ViewGroup的,由ViewGroup在他的measureChild方法中传递给子类.ViewGroup通过遍历自身所有的子View,并调用View的measure方法来完成测量 ,查看ViewGroup中源码,找到2个关键的方法:
/*** 遍历测量ViewGroup中的所有子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); } } }
/*** 测量指定View */ 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); }
下面看下View和ViewGroup中的measure方法,最终的测量时通过回调onMeasure方法实现的.这个通常都是View的子类自己实现的,我们可以通过这个方法来实现自定义View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ......../** 核心代码*/ onMeasure(widthMeasureSpec, heightMeasureSpec); ............. }
如果自定义测量过程,这个方法可以重写
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
如果没有重写这个方法,默认的会直接调用getDefaultSize来获取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; }
4.Layout
确定View在父容器中的位置,它是由父容器获取子view的位置参数后,调用子view的layout方法,将获取的参数传入来实现确定位置的.
viewRootImp中的performLayout代码 :
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ............ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ............... }
View中的layout方法代码 :
public void layout(int l, int t, int r, int b) {//....... onLayout(changed, l, t, r, b); .... }
onlayout方法 :
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
这是一个空方法,View的子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View的布局 ,我们看下ViewGroup中这个方法
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
可以看到,它里面实现了View的这个onLayout方法,并且设置为抽象方法,这样它的子类必须实现这个方法来确定包含的子View的绘制 ,我们找个ViewGroup的子类看看代码: 最常用的线性布局中的代码=>
/*** 实现了ViewGroup中的onLayout方法,分水平和垂直分别给出了绘制子View的流程*/ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }
5.Draw
Draw操作是用来将控件绘制在界面上,绘制的流程是从performDraw方法开始的,看下代码 :
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; } else if (mView == null) { return; } final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); boolean usingAsyncReport = false; if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { usingAsyncReport = true; mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { // TODO: Use the frame number pendingDrawFinished(); }); } try { boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && !canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; } } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } // For whatever reason we didn't create a HardwareRenderer, end any // hardware animations that are now dangling if (mAttachInfo.mPendingAnimatingRenderNodes != null) { final int count = mAttachInfo.mPendingAnimatingRenderNodes.size(); for (int i = 0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators(); } mAttachInfo.mPendingAnimatingRenderNodes.clear(); } if (mReportNextDraw) { mReportNextDraw = false; // if we're using multi-thread renderer, wait for the window frame draws if (mWindowDrawCountDown != null) { try { mWindowDrawCountDown.await(); } catch (InterruptedException e) { Log.e(mTag, "Window redraw count down interrupted!"); } mWindowDrawCountDown = null; } if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.setStopped(mStopped); } if (LOCAL_LOGV) { Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } if (mSurfaceHolder != null && mSurface.isValid()) { SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); } else if (!usingAsyncReport) { if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.fence(); } pendingDrawFinished(); } } }
核心的代码:
boolean canUseAsync = draw(fullRedrawNeeded);
看下draw方法 :
private boolean draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } ... }
drawSoftware方法 :
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { .... mView.draw(canvas); ...... }
根据源码追踪,看到了ViewRootImp中最终会调用view的draw方法具体绘制每个具体的View,绘制分为六个步骤: 看下View中的代码,截取出主要的代码:
public void draw(Canvas canvas) { .......... /* * 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) 1.绘制背景 2.如果需要,保存canvas图层,为fading做准备 3.绘制view内容 4.绘制view的子view 5.如果需要,绘制view的fading边缘并回复图层 6.绘制view的装饰(例如滚动条) */ }
完成...
更多相关文章
- android面试(7)-handler机制
- iOS开发 OC与java相对应的3DES加解密
- Android编程学习笔记 之 基于回调的事件传播
- Android(安卓)学习之路一:Activity之间传递数据的四种方式
- 《第一行代码--Android》读书笔记之数据存储
- android 自动化测试的傻瓜实践之旅(UI篇) -----小试身手
- 转载:Android获取其他包的Context和在任意位置获取应用程序Contex
- Android(安卓)measure过程分析
- Android(安卓)MVP 模式 项目初体验(一)