View机制深入学习(二)——View树的遍历,view深入学习
View机制深入学习(二)——View树的遍历,view深入学习
一、遍历View树的入口是ViewRootImpl的scheduleTraversal函数/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ void scheduleTraversals() { if (!mTraversalScheduled) { // 判断当前是否已经在做遍历 mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); // 这里是“Project Butter”的产物,一旦有VSYNC信号来临,mTraversalRunnable就会被调用 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); } }
1、mTraversalRunnable:
/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); try { // 最终实现View树遍历的函数 performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } }
可以看到遍历View树最终的入口函数为调用ViewRootImpl的performTraversals函数;
2、ViewRootImpl#performTraversals:
/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ private void performTraversals() { // 记录ViewRootImpl管理的View树的根节点,final修饰,避免运行过程中修改 final View host = mView; /** View树遍历的主要三个步骤measure、layout、draw **/ ...... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ...... performLayout(lp, desiredWindowWidth, desiredWindowHeight); ...... performDraw(); }
其实这三个函数仅是做了一层简单封装:
/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { // 进而调用View的measure函数 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { final View host = mView; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ...... } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } private void performDraw() { ...... Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ...... }
则上述的函数调用过程可以描述为:
/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); draw(fullRedrawNeeded);
即总共分为三个主要过程:measure过程,layout过程,draw过程;
******************************************measure过程****************************************** 二、measure过程: measure即测量视图的大小
/** \frameworks\base\core\java\android\view\View.java **/ // @params 用于确定视图的宽度和高度的规格和大小 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... onMeasure(widthMeasureSpec, heightMeasureSpec); ...... }可以看到最终是调用onMeasure来实现;而传入的参数:widthMeasureSpec,heightMeasureSpec;是用来确定视图的宽度与高度的规格和大小; 1、MeasureSpec: 每一个spec(int类型)的格式:mode(前两位)+size; MeasureSpec由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。通过MeasureSpec.specSize和MeasureSpec.specMode可以分别获取其Size与Mode值;
/** \frameworks\base\core\java\android\view\View.java **/ public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; private static final int UNSPECIFIED = 0 << MODE_SHIFT; private static final int EXACTLY = 1 << MODE_SHIFT; private static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK);} static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); ...... return makeMeasureSpec(size, mode); } public static String toString(int measureSpec) { ...... } }
其中specMode一共有三种类型,如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
很明显,MATCH_PARENT对应EXACTTLY,WRAP_CONTENT对应AT_MOST;2、View#onMeasure:
/** \frameworks\base\core\java\android\view\View.java **/ // 这里仅是View提供的默认的onMeasure实现,具体的View需要根据自身情况进行重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // AT_MOST、AT_MOST情况返回measureSpec对应的specSize,否则返回getSuggestedMinimumHeight等 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.AT_MOST: result = specSize; break; } return result; } /** 计算并设置视图界面大小 **/ protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。 这里仅是View提供的onMeasure的默认实现,对于扩展的视图,如FrameLayout等需要考虑自身的因素进行测量; 以ViewGroup为例具体来看其重写的onMeasure方法。
3、FrameLayout#onMeasure:
/** \frameworks\base\core\java\android\widget\FrameLayout.java **/ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); // 由于ViewGroup是多个View的组合,获取子View的个数 /** 判断父对象的Mode请求 **/ final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; // @value final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1); mMatchParentChildren.clear(); int maxHeight = 0; // 所有子对象中测量到的最大高度 int maxWidth = 0; // 所有子对象中测量到的最大宽度 int childState = 0; for (int i = 0; i < count; i++) { // 循环处理所有子对象 final View child = getChildAt(i); // ViewGroup中的方法,获取子View if (mMeasureAllChildren || child.getVisibility() != GONE) { // 当可见性为GONE时,不需要测量 // 递归调用此函数,对子View进行measure过程 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); /***measure过程,这里用以获取视图的最大高度与宽度 **/ 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) { // 可以看到MATCH_PARENT的View只能显示一个 mMatchParentChildren.add(child); } } } } // ViewGroup本身的Padding要计算在内 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // 计算适应当前View的最小高度值 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // 将背景图片的大小也要计算在内 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)); ...... }可以看到重写的onMeasure方法流程也大致相同,都是综合考虑父控件的尺寸大小,以及Padding,Margin等得到视图的最大Size,通过setMeasureDeimenson最后保存设置计算出来的结果。 由于ViewGroup可能包含很多子View,遍历View树通过measureChildWithMargins来执行;通过下面的函数可以看到,其会继续调用子View的measure函数,开始对子View的新一轮measure过程,直至将所有树的节点都遍历一遍。
3.1、ViewGroup#measureChildWithMargins:
/** \frameworks\base\core\java\android\view\ViewGroup.java **/ // 这里需要综合考虑Padding与Margins protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 测量子View的MeasureSpce值 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); // 递归调用子View的measure函数;如果子View是ViewGroup,则会依然按照前面的步骤再递归调用其子View的measure一直遍历下去 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } /** 可以看到measureChildWithMargins就是 measureChildren +measureChild函数,加上考虑Margins的版本 **/ 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); } } } 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); }Measure过程:由上面可见视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。
因此我们在自定义View时可以重写onMeasure函数,通过setMeasureDeimenson设置最终的视图大小。
******************************************Layout过程******************************************
三、layout过程: 通过执行performMeasure后,View Tree中各个View的大小基本确定下来,接下来需要进行另一个遍历,进行位置测量。 接下来执行host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
/** \frameworks\base\core\java\android\widget\View.java **/ public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // 同样形式调用onLayout来实现 onLayout(changed, l, t, r, b); ...... } }
l,t,r,b分别表示该View对象与父对象的左上右下边框的距离。
1、View#onLayout:/** \frameworks\base\core\java\android\view\View.java **/ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }不像View.onMeasure提供了一个默认的实现,这里的onLayout直接是一个空函数,任何继承自View的类都需要自己Override这个函数来进行布局。 而对于ViewGroup:
/** \frameworks\base\core\java\android\view\ViewGroup.java **/ @Override protected abstract void onLayout(boolean changed,int l, int t, int r, int b);其onLayout函数则是一个abstract函数,强制要求其子类必须Override。 同样以FrameLayout为例看其具体重写的方法。
2、FrameLayout#onLayout:
/** \frameworks\base\core\java\android\widget\FrameLayout.java **/ @Override 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) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // 根据对应的Layout中设置Gravity属性进行布局 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } // 递归调用子View的layout过程 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }Layout过程也是一个自View树根从上往下遍历进行layout布局的过程,需要根据具体的Layout以及Gravity等属性完成子View在父View中的布局。
******************************************Draw过程******************************************
四、draw过程: performDraw调用,来在“画板”上产生UI数据,并在适当的时机与SurfaceFlinger进行整合,最终显示到屏幕上。
/** \frameworks\base\core\java\android\view\View.java **/ 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, 如果需要的话,绘制背景 int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // 对于一些常见情况,可以跳过Step2与Step5直接进行绘制 finalint 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; } /** 在一些对速度要求不高的情况(不常见),使用完整的绘制过程*/ } /** 可以看到这两个重要的函数,并未给出具体的实现 **/ /** 重写该函数即可绘制自己自定义的View * Implement this to do your drawing. */ protected void onDraw(Canvas canvas) {} /** 重写该函数用以绘制子View * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) {} }
五、View树遍历的时机 前面涉及的View树的遍历,共经历Measure、Layout、Draw过程,而在什么情况下会触发View树: 1、View.requestLayout: View对象可以通过调用requestLayout来主动申请遍历; 1)View#requestLayout:
/** \frameworks\base\core\java\android\view\View.java **/ public void requestLayout() { ...... mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { // @value protected ViewParent mParent; mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }可以看到当需要进行Layout时,会主动调用ViewParent(ViewParent仅是一个Interface,其具体运行时类型为ViewRootImpl)的requestLayout函数:
/**\frameworks\base\core\java\android\view\ViewRootImpl **/ @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 该函数用来判断当前线程是否是主线程,因为只有主线程才能操作UI对象 checkThread(); mLayoutRequested = true; // 可以看到这里调用了前面提到的View树的遍历入口函数 scheduleTraversals(); } }可以看到这里最终调用上面提到的ViewTree遍历的入口函数scheduleTraversals函数开始View树的遍历。
/** \frameworks\base\core\java\android\view\View.java **/ public void setLayoutParams(ViewGroup.LayoutParams params) { if (params == null) { throw new NullPointerException("Layout parameters cannot be null"); } mLayoutParams = params; resolveLayoutParams(); if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onSetLayoutParams(this, params); } // 这里申请调用requestLayout来遍历View树 requestLayout(); }最终调用上面的requestLayout来遍历View树。
3、View.requestFocus:
请求View树的draw()过程,但只绘制“需要重绘”的视图。
/** \frameworks\base\core\java\android\view\View.java **/ public boolean requestFocus(int direction, Rect previouslyFocusedRect) { return requestFocusNoSearch(direction, previouslyFocusedRect); } private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || (mViewFlags & VISIBILITY_MASK) != VISIBLE) { return false; } ...... handleFocusGainInternal(direction, previouslyFocusedRect); return true; } void handleFocusGainInternal(@FocusRealDirectionint direction, Rect previouslyFocusedRect) { if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { mPrivateFlags |= PFLAG_FOCUSED; View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null; if (mParent != null) { // 遍历子View mParent.requestChildFocus(this, this); } if (mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this); } // 看下这个函数 onFocusChanged(true, direction, previouslyFocusedRect); refreshDrawableState(); } } protected void onFocusChanged(boolean gainFocus, @FocusDirectionint direction, @Nullable Rect previouslyFocusedRect) { ..... invalidate(true); ..... }可以看到最终调用下面情况invalidate来进行重绘。
4、View.invalidate: invalidate意思是“使无效”,即将当前UI界面设定为无效,从而引起重绘过程;
请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”
视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
1)View#invalidate:
/** \frameworks\base\core\java\android\view\View.java **/ public void invalidate() { invalidate(true); } void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } public void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); } void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } if (skipInvalidate()) { // 用于判断是否忽略这个invalidate操作 return; } if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { // 设置相应invalidate标志位 if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; // 设置此标志用以保证invalidate的执行 } mPrivateFlags |= PFLAG_DIRTY; // 表示由相应Dirty区域需要绘制 if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); /*********** 遍历子树*********/ p.invalidateChild(this, damage); } // Damage the entire projection receiver, if necessary. if (mBackground != null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver != null) { receiver.damageInParent(); } } // Damage the entire IsolatedZVolume receiving this view's shadow. if (isHardwareAccelerated() && getZ() != 0) { damageShadowReceiver(); } } }
2)ViewRootImpl#invalidateChildInParent:
/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/ @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); ..... final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); final float appScale = mAttachInfo.mApplicationScale; final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty(); } if (!mWillDrawSoon && (intersected || mIsAnimating)) { // 遍历View树入口函数 scheduleTraversals(); } return null; }一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
版权声明:本文为博主原创文章,未经博主允许不得转载。
用户评论