View的工作原理之View的measure、layout、draw
来源:互联网 发布:查找企业信息的软件 编辑:程序博客网 时间:2024/05/20 05:04
View作为Android在视觉上的呈现,在Android的体系中承担着重要的作用。在我们平时的开发中,会用到各种各样的View,在有特别的需求时还要用到自定义View。所以,掌握View的底层工作原理对于我们能够更好的使用View有着更大的帮助。接下来就讲一讲View的工作原理,在这里主要介绍View的测量,布局与绘制,分别对应的就是measure、layout、draw。
View的测量过程(measure):
在开始讲View的测量过程之前,我们先来认识一下MeasureSpec类,这个是View中的内部类。从注释对这个类的说明来看MeasureSpec主要通过一个int值来提供View的测量模式和测尺寸。其中高两位代表测量模式,即SpecMode,其余30位代表测量尺寸,即SpecSize;
MeasureSpec中的SpecMode有三种(以下解释来自于源码注释):
UNSPECIFIED:父容器对View不会有任何限制,View可以有自己想要的任何大小。
EXACTLY:父容器已经确定了View的精确大小,View将会得到这个精确大小。对应于match_parent和View的具体数值。
AT_MOST:View在小于父容器指定的值时可以是任意大小。对应于wrap_content;
那么对于整个UI界面的绘制过程是怎样的呢?我们先从ViewGroup中入手。在重写一些布局容器的时候,我们经常会重写onMeasure方法,这个就是绘制的入口。但同时我们在ViewGroup类并没有看到onMeasure方法是因为ViewGroup继承自View,在View中有onMeasure方法,并且ViewGroup是抽象类,onMeasure方法是在其子类中实现的,比如LinearLayout。但是在ViewGroup中有很多关于measure的方法,我们先开一下ViewGroup的measureChild方法:
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,让子View对自身测量。依次递归下去,将遍历整个View树。我们再看measureChild相关方法会发现,在其中先获取View相关的specMode和specSize,然后在决定相关的测量内容。
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) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: 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: //...... break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: //...... break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
这段代码只列出了其中一种SpecMode下的具体实现,其他两种模式的实现可以看具体源码。从代码来看,意思并不难懂,就是根据父容器的MeasureSpec以及子View的LayoutParams来决定子View的MeasureSpec。
我们已经知道整个View树的测量是从onMeasure开始的,那么是如何实现的呢,我们以LinearLayout为例,以下是LinearLayout的onMeasure方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
从代码中可以看出LinearLayout是根据布局方向来发起测量。在分析measureVertical方法我们可以发现LinearLayout通过一个mTotalHeight变量记录自身高度,而这个变量时通过子View对自身的测量进行叠加的结果。
在讲完ViewGroup的测量后,我们可以看下View的测量。通过上面的分析我们可以知道在对View进行测量的时候会调用View的measure方法。通过分析measure方法可以发现,在其中调用了onMeasure方法。在onMeasure方法中逻辑很简单,直接设置了相关的参数,所以我们可以看其中的getDefaulySize方法
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; }
在此方法中我们可以看到也是根据相应的SpecMode来设置View的大小。
View的layout过程:
Layout的作用是ViewGroup用来确定子View的位置,在ViewGroup的位置被确定后,通过调用onLayout方法来遍历所用子View并调用子View的layout方法,同时在子View的layout方法中有调用了子View的onLayout方法最终确定了子View的位置。接下来我们分析view的layout方法:
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; } 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); 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; }
从layout方法可以看出,首先会通过setFrame方法来确定View位置和大小,在此方法的注释中也可以看到。然后就调用onLayout方法。在View中我们可以看到onLayout方法是一个空方法,但是我们可以在此方法中获取View的宽高等数据。(在setFrame方法中当尺寸改变是还会调用onSizeChanged方法,同样的在此方法中我们也可以获得View的尺寸)。
View的绘制(draw):
View的绘制过程就是要将View呈现在屏幕上,我们从View的draw方法来分析。
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; } //......省略的代码可参考源码 }
从draw方法中我们可以清楚的看到,绘制过程大致分为4步。1.绘制背景;2.绘制View的内容,也就是View本身;3.绘制子View;4.绘制装饰。分别对应drawBackground方法,onDarw方法,dispatchDarw方法以及onDrawForeground方法或者onDrawScrollBars方法。
- View的工作原理之View的measure、layout、draw
- View的工作原理:measure、layout、draw
- View的工作原理(measure、layout、draw)
- View工作原理(measure、layout、draw)
- android view的讲解 之 view的工作流程(measure,layout,draw)(二)
- View的工作流程-measure、layout、draw三大流程
- View的measure, layout, draw整个过程
- View的工作原理之measure过程
- 自定义View之measure、layout、draw
- View的工作原理之draw
- Android进阶系列0—View的工作流程:measure,layout,draw小结
- View的三次measure,两次layout和一次draw
- (六)VIew的绘制过程-measure、layout、draw
- View的工作原理之layout过程
- 安卓 View 工作流程-Measure、 Layout、Draw 源码阅读
- View的工作原理(三)--View的Layout和Draw过程
- View的工作原理(1)--Measure
- Android自定义view之measure、layout、draw三大流程
- 关于 input【type=file】文件上传控件实现自定义文字及实时图片预览
- 14
- 不止支付接入,Ping++ 推出「账户系统」和「多级商户系统」
- 安卓开发之getMeasuredWidth和getWidth的区别
- assert TRACE VERIFY
- View的工作原理之View的measure、layout、draw
- httpClient中的三种超时时间设置
- Android 屏幕适配方案
- 解决Error:Execution failed for task ':app:transformClassesWithJarMergingForDebug'.
- MySQL:将远程服务器的数据库拷到本地/复制他人数据库
- 14
- 关于安装cocoapods遇到的一些坑(ERROR: Error installing cocoapods: activesupport requires Ruby version >= 2.2)
- Java多线程高并发基础面试题
- Android内存泄漏分析