UI绘制流程之测量流程

Posted 码农小风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UI绘制流程之测量流程相关的知识,希望对你有一定的参考价值。

前言

前面我们已经讲了布局的测量,现在我们来看一下,它是怎么完成布局的

View布局摆放

在performTraversals的测量被调用之后,我们继续往后看会看到performLayout的调用,那么时从此处开始的我们的具体布局的摆放那么接下来我们需要了解她的具体布局时怎么操作的

 ...
  final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) 
        performLayout(lp, mWidth, mHeight);

        // By this point all views have been sized and positioned
        // We can compute the transparent area

        if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) 
            // start out transparent
            // TODO: AVOID THAT CALL BY CACHING THE RESULT?
            host.getLocationInWindow(mTmpLocation);
            mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                    mTmpLocation[0] + host.mRight - host.mLeft,
                    mTmpLocation[1] + host.mBottom - host.mTop);

            host.gatherTransparentRegion(mTransparentRegion);
            if (mTranslator != null) 
                mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
            

            if (!mTransparentRegion.equals(mPreviousTransparentRegion)) 
                mPreviousTransparentRegion.set(mTransparentRegion);
                mFullRedrawNeeded = true;
                // reconfigure window manager
                try 
                    mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                 catch (RemoteException e) 
                
            
        

        if (DBG) 
            System.out.println("======================================");
            System.out.println("performTraversals -- after setFrame");
            host.debug();
        
    
 ...

接下来进入performLayout()

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) 
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

    final View host = mView;
    if (host == null) 
        return;
    
    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) 
        Log.v(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
    

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try 
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        int numViewsRequestingLayout = mLayoutRequesters.size();
        if (numViewsRequestingLayout > 0) 
            // requestLayout() was called during layout.
            // If no layout-request flags are set on the requesting views, there is no problem.
            // If some requests are still pending, then we need to clear those flags and do
            // a full request/measure/layout pass to handle this situation.
            ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                    false);
            if (validLayoutRequesters != null) 
                // Set this flag to indicate that any further requests are happening during
                // the second pass, which may result in posting those requests to the next
                // frame instead
                mHandlingLayoutInLayoutRequest = true;

                // Process fresh layout requests, then measure and layout
                int numValidRequests = validLayoutRequesters.size();
                for (int i = 0; i < numValidRequests; ++i) 
                    final View view = validLayoutRequesters.get(i);
                    Log.w("View", "requestLayout() improperly called by " + view +
                            " during layout: running second layout pass");
                    view.requestLayout();
                
                measureHierarchy(host, lp, mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
                mInLayout = true;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                mHandlingLayoutInLayoutRequest = false;

                // Check the valid requests again, this time without checking/clearing the
                // layout flags, since requests happening during the second pass get noop'd
                validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                if (validLayoutRequesters != null) 
                    final ArrayList<View> finalRequesters = validLayoutRequesters;
                    // Post second-pass requests to the next frame
                    getRunQueue().post(new Runnable() 
                        @Override
                        public void run() 
                            int numValidRequests = finalRequesters.size();
                            for (int i = 0; i < numValidRequests; ++i) 
                                final View view = finalRequesters.get(i);
                                Log.w("View", "requestLayout() improperly called by " + view +
                                        " during second layout pass: posting in next frame");
                                view.requestLayout();
                            
                        
                    );
                
            

        
     finally 
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    
    mInLayout = false;

在这里我们可以观察到host就是DectorView,而在下面,这里调用了host.layout。在这里会吧起始点x=0,y=0传入,然后讲测量好的宽高传入

  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;
   //这里调用了setFrame进行初始化mLeft,mRight,mTop,mBottom这四个值
    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);

        if (shouldDrawRoundScrollbar()) 
            if(mRoundScrollbarRenderer == null) 
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            
         else 
            mRoundScrollbarRenderer = null;
        

        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;

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) 
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    

在上面我门会看到对于我layout最主要做的一件事情就是对我们的上下左右四个点进行初始化,在这里时通过setFrame()

 protected boolean setFrame(int left, int top, int right, int bottom) 
    boolean changed = false;

    if (DBG) 
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) 
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position(使我们旧的信息无效化)
        invalidate(sizeChanged);
        //重新初始化定位
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;
        if (sizeChanged) 
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        

        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) 
            // If we are visible, force the DRAWN bit to on so that
            // this invalidate will go through (at least to our parent).
            // This is because someone may have invalidated this view
            // before this call to setFrame came in, thereby clearing
            // the DRAWN bit.
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        

        // Reset drawn bit to original value (invalidate turns it off)
        mPrivateFlags |= drawn;

        mBackgroundSizeChanged = true;
        mDefaultFocusHighlightSizeChanged = true;
        if (mForegroundInfo != null) 
            mForegroundInfo.mBoundsChanged = true;
        

        notifySubtreeAccessibilityStateChangedIfNeeded();
    
    return changed;

setFrame在进行初始化的时候会对比上一次是否一致,若一致则不会在此进行,若是一致,则会使我们旧的信息直接失效invalidate(sizeChanged);
那么这里布局摆放就差不多完成了,但是貌似我门没有看到DectorView的子View进行布局,这个时候我们想起之前好想是调用了当前DectorView的onLayout,也就是View当中的onLayout

  /**
 * Called from layout when this view should
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override
 * this method and call layout on each of
 * their children.
 * @param changed This is a new size or position for this view
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) 

这是我看到了一段空的代码,完全不明所以, 但是这个时候我想起来,当前在这个类当中host的原型是View, 而我们真正在使用的时候是一个DectorView
这时我门找到了

   protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
    super.onLayout(changed, left, top, right, bottom);
   
      ...

这时发现他调用了爸爸的onLayout而她的爸爸是FreamLayout所以,找到最终目标

protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);

这里请注意,有个核心问题要注意的是同之前我门所讲的测量流程, 我的布局也是同样, 每一个不同布局组件她们的实现是不一样的
而在这里我们以FreamLayout举例,
在这里他开始调用了一个
layoutChildren()

  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();

    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;

            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;
            

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        
    

这个时候我们会发现,当前我们的组件在不断的迭代当前的子view,然后让她们开始调用自己layout方法进行定位,所以直接从此处可以看出来,当前我们的布局摆放流程实际上是,先得到顶层, 顶层自己先开始layout进行布局定位,然后调用onLayout调用子view让子view调用自己的layout对自己进行定位以达到定位的所有目的,

总结:

那么其实我门只要清楚了当前的绘制流程和布局流程,我门需要开发自己自定义的布局其实实际上就只需要添加我门自己的业务代码,不管是FreamLayout,还是LinearLayout等官方提供出来的布局组件, 都是依照这套机制来玩的, 只不过是添加了她们的业务,实现了相对应的效果。

所以,至此,绘制流程ok! 好啦,至此为止,这一篇文章我就介绍完毕了,在这里希望你可以收获更多的知识,我们下一期再见!

以上是关于UI绘制流程之测量流程的主要内容,如果未能解决你的问题,请参考以下文章

反思|Android View机制设计与实现:测量流程

Android自定义view之view显示流程

View绘制流程二:测量布局绘制

View绘制流程二:测量布局绘制

View绘制流程二:测量布局绘制

反思|Android View机制设计与实现:布局流程