Android View的second layout pass

  1. Second layout pass的触发: 首先在View的requestLayout()中:

    • 如果之前有mMeasureCache,因为要重新layout,之前的cache已经无效了,直接mMeasureCache.clear()来清除缓存.
    • 如果该View已经被attach了(mAttachInfo != null)并且AttachInfo中的mViewRequestingLayout(Used to track which View originated a requestLayout() call, used when requestLayout() is called during layout.)不是该null**, 获取ViewRootImpl, 如果当前的ViewRootImpl已经在layout过程中了(isInLayout()), 那么会调用viewRoot.requestLayoutDuringLayout(this), 如果返回false(返回true代表这个请求在这一帧被处理,返回false代表这个请求会在这一帧被skip,在下一帧处理),即这个layout请求会在下一帧被处理,直接return. 如果返回true, 那么继续向下,会将mAttachInfo.mViewRequestingLayout = this.
    • mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; 重新layout和draw
    • 如果有parentv并且parent不是isLayoutRequested()((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT, 注意这个flag在forceLayout()中也会被设置上), 那么会调用parent的requestLayout()(这一步会和mAttachInfo.mViewRequestingLayout一起协同工作, 及这一帧如果parent发起过,其child再发起,mViewRequestingLayout还是parent).
    • 如果已经attach并且mAttachInfo.mViewRequestingLayout == this, 那么mAttachInfo.mViewRequestingLayout = null,(注意,这一步很关键,mViewRequestingLayout在什么时候会是this呢, 根据前面的分析,如果当前的mViewRequestingLayout是null,如果!viewRoot.isInLayout()或者viewRoot.isInLayout()但是viewRoot.requestLayoutDuringLayout(this)返回true(代表这次RequestInLayout被处理了)就会将mAttachInfo.mViewRequestingLayout设置为this, 进而在最后将mViewRequestingLayout重新设置为null)
    • 有上面的分析可以看到, mViewRequestingLayout其实保存的是一个暂态, 因为在一个ChildView触发requestLayout()第一步return的地方,mViewRequestingLayout必定是null, 而如果能走到最后, mViewRequestingLayout还是会被设置为null. 这个变量的意义在于函数中间调用mParent.requestLayout()时,在parent的requestLayout的过程中,mViewRequestingLayout这个值将是发起layout请求的childView., 所谓的Layout中发起RequestLayout这个操作,其实和mViewRequestingLayout没有任何关系, 纯粹是判定viewRoot.isIn()罢了,如果isInLayout()那么意味着这是Layout中发起的RequestLayout操作,需要进一步的viewRoot.requestLayoutDuringLayout(this)判定,对其返回结果的判断也迎合了这个函数的注释: 如果返回true, 那么代表着这个request会在本轮的second pass中进行处理(逻辑是继续向下走,和普通的requestLayout流程一样),返回false代表这个request被放到了下一帧,因此在这里直接return,这次requestLayout就被取消了.
    • mViewRequestingLayout的作用是在requestLayout函数复数次递归调用中标识发起layout请求的view, 这样在复数次相同调用中才能区分这次调用是view发起的,还是view触发的parent发起的.
      1. ViewRootImpl中的requestLayoutDuringLayout(final View view):
    • 如果view没有parent或者view没有被attach. 直接return true.
    • 在mLayoutRequesters中添加此view(要考虑已经在的情况).
    • 如果mHandlingLayoutInLayoutRequest,return true(Let the request proceed normally; it will be processed in a second layout pass if necessary),否则false(Don’t let the request proceed during the second layout pass. It will post to the next frame instead. 结合上面requestLayout(..)的实现可以看出,如果返回的是true,该次requestLayout会和正常的一样,如果返回的是false, 这次requestLayout就被中止取消了,但是呢,在mLayoutRequesters中已经保存了因此后面为其还会发起requestLayout).
  2. mHandlingLayoutInLayoutRequest在ViewRootImpl中代表的是第二次layout正在进行(这个标志位在ViewRootImpl的requestLayout中会被用来做判断,如果true, 那么不会发起scheduleTraversals()):

    • 在performLayout(…)中, 在host就是最外层的View, ViewRootImpl的mView.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()), mInLayout = false -> viewRoot.isInLayout();之后还会有一次判断:
    • 如果mLayoutRequesters里有成员,那么说明在第一次的layout pass中有继续发起layout请求的view,需要进行second layout pass.
    • getValidLayoutRequesters(…, false)会返回符合second layout pass的成员,其判断的一个关键点是View是否有PFLAG_FORCE_LAYOUT.
    • 如果有valid的Requester,那么会将mHandlingLayoutInLayoutRequest设置为true代表着第二次layout pass的开始
    • 对每个valid的requester view调用 view.requestLayout(),根据之前的分析,这一轮的requestLayout()是传递不上去的,因为已经在second layout pass了,其作用主要就是把该view以及其向上的所有parent的PFLAG_FORCE_LAYOUT设置上,这样后来的host.layout才能触发这些View的layout和onLayout
    • measureHierarchy(host, lp, mView.getContext().getResources(),
      desiredWindowWidth, desiredWindowHeight);
    • mInLayout = true 代表正在layout中;
    • host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()) 完了之后mHandlingLayoutInLayoutRequest = false**代表着second layout pass的结束**
    • getValidLayoutRequesters(…, true)这次获取要在下一帧进行layout的requseter view(注意,上面调用getValidLayoutRequesters(…)会将mLayoutRequesters进行clear, 这一次的mLayoutRequesters中装填的是second layout pass中的又发起layout请求的View,没办法了,不可能为他们进行third layout pass(否则可能会在这一帧发起过多的layout pass),那就只能放在下一帧了), 如果有,那么会post一个Runnable,这个Runnable会在下一帧的时候对这些View调用requestLayout
    • 最后mInLayout = false.
    • 一个疑问: 在处理可以被放到second layout pass的View时,scheduleTraversals()并没有被block住,但是这些View的layout需求其实在这一帧的sencod layout pass已经可以满足了,为什么还要schedule一个mTraversalRunnable?
  3. getValidLayoutRequesters(ArrayList layoutRequesters,
    boolean secondLayoutRequests):

    • walks through the list of views that requested layout to determine which ones still need it, based on visibility in the hierarchy and whether they have already been handled (as is usually the case with ListView children)
    • secondLayoutRequests会参与判断是否将View加入validRequster, 如果!secondLayoutRequests, 那么就只有PFLAG_FORCE_LAYOUT的View才能进一步被检查, 否则任何有parent的View都可以参与进一步检查.
    • 会对每个View进行一次自底向上的遍历, 确保其不是在一个GONE的hierarchy中,即View自己可能不是gone,但是他的parent是gone, 通过的才能是valid
    • 在View全部判断完以后, 如果!secondLayoutRequests, 那么会自底向上的将View及其parent的PFLAG_FORCE_LAYOUT flag全部去掉.
  4. measureHierarchy(final View host, final WindowManager.LayoutParams lp,
    final Resources res, final int desiredWindowWidth, final int desiredWindowHeight):

    • 会调用performMeasure(…) (可见measure也是和layout一样会被触发多次)
  5. layout(…)函数:

    • 先看ViewGroup的: 如果!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())(当前没有SuppressLayout 并且当前没有mTransition(LayoutTransition)或者mTransition不会因此layout的change),那么才能执行super.layout(l, t, r, b)(就是View的实现), 否则这次layout就被supress了: mLayoutCalledWhileSuppressed = true, 这个值会在后边会检查并且可能会发起这一次layout
  6. View的layout(…)

    • 如果有PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT这个flag, 那么需要执行一次onMeasure(..) 这个flag会在之前measure()的cahce有效的情况下被设置上
    • 根据isLayoutModeOptical(mParent)(对View是false,对ViewGroup则需要判断 LAYOUT_MODE_OPTICAL_BOUNDS这个flag)来进行setOpticalFrame(l, t, r, b) /setFrame(l, t, r, b)并返回是否位置change了.
    • setFrame(…)中, 如果left/right/top/bottom任何发生了变化,会判断是否发生了sizechange(只是长和宽的变化), 用此作为参数调用invalidate(sizeChanged(参数的含义是是否invalidateCache))
      • 如果采用了mDisplayList(硬件加速模式), 那么需要根据新的位置来对DisplayList进行位移.
      • 如果View是可见的, mPrivateFlags |= PFLAG_DRAWN + invalidate(sizeChanged) + invalidateParentCaches()
    • 如果setFrame返回的是changed, 或者PFLAG_LAYOUT_REQUIRED, 那么:
      • onLayout(changed, l, t, r, b)
      • mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
      • 回调OnLayoutChangeListener中的listener.
    • mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  7. 前面requestLayout(…)/forceLayout(..)设置的PFLAG_FORCE_LAYOUT生效的方式是在measure()中,如果PFLAG_FORCE_LAYOUT的话,会为View加上PFLAG_LAYOUT_REQUIRED这个flag(如果measure(…)中传入的MeasureSpec发生了变化(这个MeasureSpec不代表真正被set到View上的的MeasureSpec)也会触发这个逻辑)

你可能感兴趣的:(android)