View的工作流程
来源:互联网 发布:微信公众平台修改域名 编辑:程序博客网 时间:2024/05/16 17:38
View的工作流程
measure
测量View的宽高;measure完成之后,通过getMeasuredWidth(),getMeasuredHeight()可以获取控件的宽高,大部分View宽高就是测量的宽高
layout
确定View在父容器中放置的位置,四个顶点坐标及View实际宽高
完成后可通过getTop(),getBottom(),getLeft(),getRight()获取具体坐标;getWidth(),getHeight()获取控件真实宽高
draw
负责将View绘制在屏幕上
measure
ViewGroup
它没有measure方法,measureChildren与其类似
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; //循环测量child for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } }}
测量子控件measureChildren
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { //获取parent布局参数 final LayoutParams lp = child.getLayoutParams(); //宽度MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); //高度MeasureSpec final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); //调用child的测量方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
//Dimension尺寸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: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } 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 asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
表1-1 普通View的MeasureSpec创建规则
childLayoutParams
childSize EXACTLY
childSize EXACTLY
childSize match_parent EXACTLY
parentSize AT_MOST
parentSize UNSPECIFIED
0 wrap_parent AT_MOST
parentSize AT_MOST
parentSize UNSPECIFIED
0
LinearLayout
查看实例的具体实现
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ if (mOrientation == VERTICAL) { //测量垂直布局 measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}
方法:measureVertical
// See how tall everyone is. Also remember max width.遍历子控件for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); ...... // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if needed). measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }}
一般在onLayout中获取测量的宽/高会比较准确
在activity启动的时候,获取view测量的宽高
1.Activity/View#onWindowFocusChanged
@Overridepublic void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); view.getMeasuredWidth(); view.getMeasuredHeight();}
View已经初始化完毕,宽/高已经准备好了。这个时候去获取宽高是没有问题的。onWindowFocusChanged会被调用多次,当activity获取焦点或失去焦点的时候都会被调用。具体就是当activity继续执行和暂停执行都会被调用,onResume和onPause也会调用
2.view.post(runnable)
protected void onStart(){ super.onStart(); view.post(new Runnable() { @Override public void run() { view.getMeasuredHeight(); view.getMeasuredWidth(); } });}
通过post可以将一个runnable投递到消息队列尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了
3.ViewTreeObserver
ViewTreeObserver可以完成,当View树的状态发生改变或者View树内部的View可见性发生改变,onGlobalLayout将被回调。但可能会被调用多次
protected void onStart() { super.onStart(); ViewTreeObserver viewTreeObserver = view.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); view.getMeasuredHeight(); view.getMeasuredWidth(); } });}
4.view.measure(widthSpec,heightSpec)
根据View的LayoutParams来分
match_parent
具体数值
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);view.measure(widthMeasureSpec,heightMeasureSpec);
- wrap_parent
MeasureSpec的size最大值是30个1,2^30-1=(1<<30)-1,所以用最大的值去构造MeasureSpec
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.EXACTLY);int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.EXACTLY);view.measure(widthMeasureSpec,heightMeasureSpec);
View
View的measure方法是final的,所以他不能被子类重写。measure方法中调用了
onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //设置测量的宽、高 //protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
方法:getDefaultSize
/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * 应用程序返回一个默认的大小.如果没有强制约束,则会使用建议的size.如果MeasureSpec允许,将可以让size更大 * @param size Default size for this view * @param measureSpec Constraints imposed by the parent(parent的强制约束) * @return The size this view should be. */public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); //View测量后的大小 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;}
UNSPECIFIED一般用于系统内部的测量过程
具体测量过程如下,
方法:getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() { //没有背景:返回xml中android:minWidth,未指定则为0 //有背景:返回android:minWidth、背景宽度的较大值 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}//stream.addProperty("measurement:minWidth", mMinWidth);//xml中的配置属性android:minWidthpublic int getMinimumWidth() { return mMinWidth;}
Drawable.java
public int getMinimumWidth() { // final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0;}
方法:getSuggestedMinimumHeight()
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}
直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身大小,否则在布局中使用wrap_content与match_content是一样的-见表1,
像系统的TextView、ImageView都做了处理。可查看源码
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if(widthMode == MeasureSpec.AT_MOST&&heightMode == MeasureSpec.AT_MOST){ setMeasuredDimension(mWidthSize mHeight); }else if(widthMode == MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, heightSize ); }else if(heightMode == MeasureSpec.AT_MOST){ setMeasuredDimension(widthSize, mHeight); } }
Layout
layout确定view的位置,onlayout确定所以子元素的位置
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;}
setFrame设定View顶点的四个值,View和ViewGroup并没有真实实现onLayout。
LinearLayout
LinearLayout分为水平和垂直布局
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);}}
void layoutVertical(int left, int top, int right, int bottom) { ...... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } }}
此方法会遍历所有子元素并调用setChildFrame来为子元素指定位置,其中childTop会逐渐增大,后面元素就会放置到下方。childWidth、childHeight是测量高度
private void setChildFrame(View child, int left, int top, int width, int height) { //调用View的layout方法 child.layout(left, top, left + width, top + height);}
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; }}
- 绘制背景 background.draw(canvals)
- 绘制自己(onDraw)
- 绘制children(dispatchDraw)
- 绘制装饰(onDrawScrollBars)
View有一个特殊的方法
如果一个View不需要绘制任何内容,那么设置这个标记位为true之后,系统会进行相应的优化。默认情况下,View没有开启这个标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations(优化计算). By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}
注意
1.直接继承View或ViewGroup的控件,在onMeasure中对wrap_content要做特殊处理。
2.继承自View,在draw方法中处理padding,否则padding值无法起作用
3.继承自ViewGroup,要在onMeasure、onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效
4.View中如果有线程或者动画,需要及时停止View#onDetachedFromWindow
- View的工作流程
- View的工作流程
- View的工作流程
- View的工作流程
- View的工作流程
- Android--View的工作流程
- Android View的工作流程
- 自定义view之view的工作流程
- 自定义View--View的工作流程
- View的工作原理(二)----View的工作流程
- View的工作流程(layout过程)
- View的工作流程---Measure过程
- View的工作流程-对MeasureSpec认识
- View的工作流程-Layout过程
- Android view 工作流程
- View工作流程
- View工作流程
- View总结-工作流程
- C语言细节之定义与声明
- PTN910设备的安全特性
- Apache配置虚拟域名后localhost无法正常访问
- vassitx使用技巧(用了就再也没法放弃)
- 链表创建为什么需要使用内存分配?
- View的工作流程
- ACCESS里的对象
- SharedPreferences
- 程序员的成长阶梯和级别定义
- 一个比较奇葩的bug
- React Native动画的锚点anchorPoint
- Storm 使用经验与性能优化(二)
- Mybatis物理分页插件报错: duplicate column ‘xxx'的原因分析与解决
- 分享微信微博脚本