最近在撸
Golang
有点上火了,来整理下安卓源码资料分析结果基于
Audroid API 26
requestLayout()
源码分析
假如在一个页面上有个按钮,点击按钮就对一个
view.requestLayout()
,这个view
执行的方法如下:
InvalidateTextView------onMeasure InvalidateTextView------onMeasure InvalidateTextView-------layout InvalidateTextView--------onLayout InvalidateTextView----------draw InvalidateTextView------------onDraw
view.requestLayout()
方法的详情
@CallSuper public void requestLayout() { // 清除绘制的缓存 if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { //只有在布局逻辑中触发请求,如果这是请求它的视图,而不是其父层次结构中的视图 ViewRootImpl viewRoot = getViewRootImpl(); //如果连续请求两次,其中一次自动返回! if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } //todo 为当前view设置标记位 PFLAG_FORCE_LAYOUT mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { // todo 向父容器请求布局 这里是向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理 mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }
1、如果缓存不为
null
,清除绘制的缓存
if (mMeasureCache != null) mMeasureCache.clear();
2、这里判断了是否在
layout
,如果是,就返回,也就可以理解为: 如果连续请求两次,并且其中的一次正在layout
中,其中一次返回!这样做是节约性能
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { //只有在布局逻辑中触发请求,如果这是请求它的视图,而不是其父层次结构中的视图 ViewRootImpl viewRoot = getViewRootImpl(); //如果连续请求两次,其中一次自动返回! if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; }
3、 为当前
view
设置标记位PFLAG_FORCE_LAYOUT
,关于|=
符号:a|=b
的意思就是把a和b按位或然后赋值给a 按位或的意思就是先把a和b都换成2进制,然后用或操作,相当于a=a|b
mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED;
4、向父容器请求布局,即调用
ViewGroup
父容器的requestLayout()
方法,为父容器添加PFLAG_FORCE_LAYOUT
标记位,而父容器又会调用它的父容器的requestLayout()
方法,即requestLayout()
事件层层向上传递,直到DecorView
,即根View
,而根View
又会传递给ViewRootImpl,
也即是说子View的requestLayout()f
事件,最终会被ViewRootImpl.requestLayout()
接收并得到处理
if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); }
5、
ViewRootImpl.requestLayout()
方法详情@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检查是否在主线程,不在的话,抛出异常 checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
// requestLayout() 会调用这个方法 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 最终调用的是这个方法 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
2 、最终走到这个方法来
ViewRootImpl.scheduleTraversals()
,在其中可以看到一行非常有意思的代码mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
,其中有个对象mTraversalRunnable
,这样下去就会重新的测量、布局和绘制;具体的流程可以看这篇文章Android源码分析(View的绘制流程)
1、检查是否在主线程,不在的话,抛出异常
checkThread();
有个问题,我先抛出结论,
requessLayout() 、invalidate()、postInvalidate()
最终的底层调用的是ViewRootImpl.scheduleTraversals()
的方法,为什么仅仅requessLayout()
才会执行onMeasure() onLayout() onDraw()
这几个方法?关于
view.measure()
方法:在前面我们知道mPrivateFlags |= PFLAG_FORCE_LAYOUT
所以forceLayout = true
,也就是会执行onMeasure(widthMeasureSpec, heightMeasureSpec);
,同时执行完了以后呢,最后为标记位设置为mPrivateFlags |=PFLAG_LAYOUT_REQUIRED
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... // requestLayout的方法改变的 mPrivateFlags |= PFLAG_FORCE_LAYOUT; 所以 forceLayout = true final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; ... if (forceLayout || needsLayout) { ... if (cacheIndex < 0 || sIgnoreMeasureCache) { //最终会走到这方法来 onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // 接着最后为标记位设置为PFLAG_LAYOUT_REQUIRED mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } ... }
关于
view.layout()
方法:判断标记位是否为PFLAG_LAYOUT_REQUIRED
,如果有,则对该View
进行布局,也就是走到onLayout(changed, l, t, r, b);
,最后清除标记mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
@SuppressWarnings({"unchecked"}) 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); //判断标记位是否为PFLAG_LAYOUT_REQUIRED,如果有,则对该View进行布局 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } // onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED标记位 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; } // //最后清除PFLAG_FORCE_LAYOUT标记位 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; ... }
以上就是
requestLayout()
的分析的结果:view
调用了这个方法,其实会从view
树重新进行一次测量、布局、绘制这三个流程。做了一张图
requestLayout()的原理.jpg
invalidate()源码分析
view.invalidate()
;继承一个Textview,然后重写方法,设置一个but,同时请求方法,打印日志:请求一次的输出的结果
InvalidateTextView----------draw InvalidateTextView------------onDraw
方法详情 :
view.invalidate()
public void invalidate() { invalidate(true); }
该视图的绘图缓存是否也应无效。对于完全无效,设置为true,但是如果视图的内容或维度没有改变,则可以设置为false。
public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }
invalidateInternal()
方法详情:其实关键的方法就是invalidateChild()
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView != null) { mGhostView.invalidate(true); return; } // 判断是否可见,是否在动画中,是否不是ViewGroup,三项满足一项,直接返回 if (skipInvalidate()) { return; }//根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘 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)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } //设置PFLAG_DIRTY标记位 mPrivateFlags |= PFLAG_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(); } } } }
作者:豌豆射手_BiuBiu
链接:https://www.jianshu.com/p/11345ef2b1ef
共同學(xué)習(xí),寫(xiě)下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章