View的工作原理
来源:互联网 发布:倒放软件下载 编辑:程序博客网 时间:2024/05/15 22:13
相关概念:
ViewRoot:ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程都是在viewroot中完成。
View的绘制流程:
View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure,layout,draw三个过程才最终将一个View绘制出来,performTraversals会一次调用performMeasure,performLayout和performDraw三个方法,这三个方法分别会完成view的measure,layout,draw的流程,在measure方法中,又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递给子容器,这样就完成了一次测量接着子元素会重复父容器的measure的测量过程,如此反复的完成整个View树的过程,同理performLayout的执行原理和performDraw的执行原理和performMeasure的原理类似。
measure过程中决定了View的宽和高,Measure完成后,可以通过getMeasureWidth和getMeasureHeight方法来获取到View测量后的宽和高,在几乎所有情况下,它都等于最终的宽/高,但有一些特殊的情况。Layout过程中决定了View四个顶点的坐标,和实际View的宽和高。完成后可以通过getTop,getBottom,getLeft,getRight来拿到四个顶点的位置,并且可以通过getWidth,和getHeight完成最终的宽和高。Draw过程决定View的显示,只有Draw以后View才会显示在屏幕上。
DecorView作为顶级View,一般情况它内部包含一个竖直方向的Linearlayout,在这个LinearLayout里面有上下两个部分,上面是标题栏,下面是内容栏,在Activity中我们设置的setContentView所设置的View,实际上是被加载到了内容栏上了,而内容栏的id叫content,我们的布局确实又加载到了content上,因此叫做setContentView。,实际上DecorView是一个FrameLayout,View事件都是经过DecorView才传到我们的View。
理解MeasureSpec
MeasureSpec可以翻译为测量规格,在测量中,系统会将View的LayoutParams根据父容器所施加的规则转换成相应的MeasureSpec,然后再根据MeasureSpec测量出View的宽和高。MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指某种测量模式下的测量出来的大小,
MeasureSpec有三种模式:
UNSPECIFIED
父容器不对View有任何限制,要多大就多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY
父容器已经测量出来View的精确大小,这个时候View的最终大小就是MeasureSpec测量的值,它对应于LayoutParams中的Match_parent和具体数值的两种情况。
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体多少,要看View的具体情况,它对应于LayoutParams的wrap_content这种情况。
View的宽高受View自身的LayoutParams和父容器的约束所决定,转换成相应的MeasureSpec,一旦MeasureSpec确定,onMeasure中就可以View测量的宽和高。
对于普通的View来说,这里是指我们布局中的View,View的measure过程是由ViewGroup传递过来的,先看一下ViewGroup的measureChiled
View的工作流程:
View的工作流程是指measure,layout,draw这三大流程,即测量,布局和绘制,其中measure是测量view的宽和高,layout是确定View控件四个顶点的位置,而draw则将View绘制在屏幕上。
View的measure过程:
View的measure过程由其measure方法来完成,measure方法是一个final类型,这意味着子类不能去重新这个方法,而measure方法中会调用onMeasure这个方法,因此我们去重写onMeasure这个方法。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),widthMeasureSpec)); }
setMeasureDimension会设置View宽和高的测量值,下面看一下getDefaultSize()这个方法:
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; }
如果测量模式是MeasureSpec.UNSPECIFIED 则默认值为size,是其它两种模式的话,就用测量的值,测量的值为specSize.
下面看一下,getSuggestedMinimumWidth这个方法:
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
返回值得大小取决于控件view有没有设置背景,有设置背景,那么view的宽或者高的大小为mMinWidth和背景的最小宽或者高,如果没有背景,那么view的宽/高的测量返回这就是mMinWidth这个值,这个值为0。
下面我们实际书写写关于onMeasure方法的代码:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int mWidth = 0;//这是我们想要设置的宽度 int mHeight = 0;//这是我们想要设置的高度 int widthMode=MeasureSpec.getMode(widthMeasureSpec); int heightMode=MeasureSpec.getMode(heightMeasureSpec); int widthSize=MeasureSpec.getSize(widthMeasureSpec); int heightSize=MeasureSpec.getSize(heightMeasureSpec); if(widthMeasureSpec==MeasureSpec.AT_MOST && heightMeasureSpec==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, mHeight); }else if(widthMode==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, heightSize); }else if(heightMode==MeasureSpec.AT_MOST){ setMeasuredDimension(widthSize, mHeight); } }
ViewGroup的测量过程:
对于ViewGroup来说,除了完成自己的measure过程外,还要遍历去测量子View的measure方法,各个子元素在递归去执行这个过程,和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的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); } } }
从代码中可以看出来,在measureChildren方法中,会遍历所有子View,然后通过measureChild这个方法去测量每一个子View。
measureChild方法如下:
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的measure方法,在最后调用onMeasure方法,这又回到了View的measure过程。
实际情况可以这么写:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 显示的设置一个宽度 */ if (!once) { LinearLayout wrapper = (LinearLayout) getChildAt(0); menu = (ViewGroup) wrapper.getChildAt(0); ViewGroup content = (ViewGroup) wrapper.getChildAt(1); mMenuWidth = mScreenWidth - mMenuRightPadding; mHalfMenuWidth = mMenuWidth / 2; menu.getLayoutParams().width = mMenuWidth; content.getLayoutParams().width = mScreenWidth; } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
在onMeasure完后可以通过getMeasureWidth和getMeasureHeight来得到测量view的宽和高,在某些极端情况下,系统可能需要多次测量,在这种情况下,我们通过getMeasureWidth和getMeasureHeight拿到的宽和高是不准确的,一个较好的习惯是在onLayout方法中获取View的测量宽或者高。
可能遇到的问题:
View的onMeasure和Acitivity的生命周期是不同步的,在onCreat,onStart,onResume中getMeasureWidth/getMeasureHeight获得宽和高,很可能是0,这里给出了4中解决方法。
1.Activity中的onWindowFocesChanged方法,通过上面的方法可以获得测量后的宽高。单这个方法会被调用多次。
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if(hasFocus){ int mWidth=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); } }
2.view.post(runnable)方法: 通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也初始化好了。
view.post(new Runnable() { @Override public void run() { int mWidth=view.getMeasuredWidth(); int mHeight=view.getMeasuredHeight(); } });
3.ViewTreeObserver 使用OnGlobalLayoutListener这个接口,当View树的状态发生改变或者View树内部View的可见性发生改变的时候,onGlobalLayout方法会被回调,需要注意的是随着View树的改变,这个方法会被调用多次:
ViewTreeObserver observer=view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int mWidth=view.getMeasuredWidth(); int mHeight=view.getMeasuredHeight(); } });
4.view.measure(int widthMeasureSpec,int heightMeasureSpec ) 比较复杂,这里不做解释了。
Layout过程:
Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout方法中会遍历所有子元素,并调用layout方法。而layout方法中onLayout方法会被调用,所以我们重写这个方法。Layout方法和Measure相比要简单许多,layout是确定View本身的位置,而onLayout方法会确定所有子元素的位置,先看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; }
在第16行走onLayout方法,layout方法的流程是:首先通过setFrame方法来设定View四个顶点的位置,即初始化了,l,t,r,b这四个值,View的这四个顶点一确定,那么View在父容器中的位置也就确定了,接着会被调用onLayout方法,这个方法的用途是为了确定子View在父控件中的位置,和onMeasure类似,onLayout的实现和具体的布局有关,view和viewGroup军没有实现onLayout方法,因此我们随便找一个父控件看onLayout方法,例如LinearLayout中的onLayout方法:
@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); } }
Draw过程:
draw过程的原里就简单了,它的作用是将View绘制到屏幕上,View的绘制过程遵循如下几步:
1.绘制背景 2.绘制自己 3.绘制children 4.绘制装饰。
draw方法的代码如下:
public void draw(Canvas canvas) { if (mClipBounds != null) { canvas.clipRect(mClipBounds); } 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) { final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } // 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); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(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); 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); 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); 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); canvas.drawRect(right - length, top, right, bottom, p); } canvas.restoreToCount(saveCount); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } }
View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历所有子元素的draw方法,如此draw事件就一层层的传递下去。
自定义View所涉及到的一些有关问题:
1.如果有必要让你的View支持padding
这是因为直接继承View的控件,如果不在draw方法中处理paddding,那么padding属性无法起作用,另外直接继承自ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
在onDraw中考虑padding的代码:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int paddingLeft=getPaddingLeft(); int paddingRight=getPaddingRight(); int paddingTop=getPaddingTop(); int paddingBottom=getPaddingBottom(); int mWidth=getWidth()-paddingLeft-getPaddingRight(); int mHeight=getHeight()-paddingTop-paddingBottom; canvas.drawCircle(mWidth/2, mHeight/2,50,paint); }
为了使用自定义属性,必须在布局文件中添加schemas声明:xmlns:app=”http:android.com/apl/res-auto”,在这个声明中,app是自定义的,可以换成任意名字,但是自定义属性xml使用的时候必须和这个一致,也有另外一种声明方式,如:schemas:xmlns:app=”http://schemas.android.com/apk/res/com.ryg.chapter_4”,这种方式是apk/res后面附加应用的包名,但是这种方式并没有本质的区别。
继承ViewGroop的onMeasure和onLayout方法:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int mWidth=0; int mHeight=0; int mChildCount=getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpec=MeasureSpec.getSize(widthMeasureSpec); int heightSpec=MeasureSpec.getSize(heightMeasureSpec); int widthMode=MeasureSpec.getMode(widthMeasureSpec); int HeightMode=MeasureSpec.getMode(heightMeasureSpec); if(mChildCount==0){ setMeasuredDimension(0, 0); }else{ if(MeasureSpec.AT_MOST==widthMode && MeasureSpec.AT_MOST==HeightMode){ setMeasuredDimension(mWidth, mHeight); }else if(MeasureSpec.AT_MOST==widthMode){ setMeasuredDimension(mWidth, heightSpec); }else if(MeasureSpec.AT_MOST==HeightMode){ setMeasuredDimension(widthMode, mHeight); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); int childLeft=0; int childCount=getChildCount(); for(int i=0;i<childCount;i++){ View childView=getChildAt(i); if(childView.getVisibility() !=View.GONE){ int childWidth=childView.getMeasuredWidth(); childView.layout(childLeft, 0, childLeft+childWidth, childView.getMeasuredHeight()); childLeft+=childLeft; } } }
参考任玉刚老师的Android开发艺术探索一书中的View的原理,任大神的博客
- View的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View 的工作原理
- View的工作原理
- view的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View 的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- View的工作原理
- linux 网络设置
- Android 在 SElinux下 如何获得对一个内核节点的访问权限
- 微信公众平台java开发教程之验证服务器有效性
- 亚马逊云VPS AWS更改LINUX为root权限密码登陆
- 文章标题
- View的工作原理
- Oracle 创建 DBLink 的方法
- 入门Android开发--百度地图开发
- 查找最后一个月季的索引
- 怎么在MySQL官网下载java连接MySQL数据库的驱动jar包
- 购物网站流程图(收藏)
- 一些常用的web开发软件
- maven编译spark报PermGen space解决办法
- Node.js 究竟是什么?