美文网首页
View的可见性和绘制流程

View的可见性和绘制流程

作者: 雨打空城 | 来源:发表于2018-09-25 14:42 被阅读85次

    view的绘制流程主要有三个部分,onMeasure,onLayout,onDraw. 其中,onMeasure是决定该view的大小,onLayout决定view的位置,onDraw决定view的显示。

    除了在该view第一次出现在屏幕时,会调用这几个方法,其他的一些事件也会导致这几个方法的调用。 今天主要研究设置view的可见性和键盘不同模式下弹起和隐藏时,导致哪些重要的方法被调用。

    另外其中有一个接口ViewTreeObserver.OnGlobalLayoutListener经常被我们用到,它的回调方法onGlobalLayout官方解释:

    Callback method to be invoked when the global layout state or the visibility of views within the view tree changes

    即:全局的布局或者该view树下的view的可见性发生变化时会被触发。

    那么接下来就用具体的代码和日志来验证该回调的调用。
    首先,先从源码看一下该回调在哪里触发:

    一. onGlobalLayoutListener

    1. 该回调是在ViewTreeObserver的方法dispatchOnGlobalLayout()中被调用
    那么dispatchOnGlobalLayout在哪里被分发呢?

    #ViewTreeObserver
     public final void dispatchOnGlobalLayout() {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
            if (listeners != null && listeners.size() > 0) {
                CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
                try {
                    int count = access.size();
                    for (int i = 0; i < count; i++) {
                        access.get(i).onGlobalLayout();
                    }
                } finally {
                    listeners.end();
                }
            }
        }
    

    2. 在ViewRootImpl的方法peformTraversals()中调用
    如果标志位didLayouttrue,那么会调用performLayout,一旦didLayout = true,那么标志位triggerGlobalLayoutListener一定为true,就必定会调用ViewTreeeObserver.dispatchOnGlobalLayout()方法。

    #ViewRootImpl.performTraversals()
      final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
      boolean triggerGlobalLayoutListener = didLayout
                    || mAttachInfo.mRecomputeGlobalAttributes;
      if (didLayout) {
                performLayout(lp, mWidth, mHeight);
                ...
        }
      if (triggerGlobalLayoutListener) {
                mAttachInfo.mRecomputeGlobalAttributes = false;
                mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    

    所以得出结论:能引起重新布局的方法都将会触发onGlobalLayoutListener事件,且该事件在onLayout回调之后。

    二. View.setVisibility() 方法

    查看源码,发现view.setVisibility方法里面仅仅调用了setFlag()

       @RemotableViewMethod
        public void setVisibility(@Visibility int visibility) {
            setFlags(visibility, VISIBILITY_MASK);
        }
    

    继续进setFlags()方法中看

     void setFlags(int flags, int mask) {
            int old = mViewFlags;
            mViewFlags = (mViewFlags & ~mask) | (flags & mask);
            int changed = mViewFlags ^ old;
            ...
            final int newVisibility = flags & VISIBILITY_MASK;
            if (newVisibility == VISIBLE) {
                if ((changed & VISIBILITY_MASK) != 0) {
                    /*
                     * If this view is becoming visible, invalidate it in case it changed while
                     * it was not visible. Marking it drawn ensures that the invalidation will
                     * go through.
                     */
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(true);
    
                    needGlobalAttributesUpdate(true);
    
                    // a view becoming visible is worth notifying the parent
                    // about in case nothing has focus.  even if this specific view
                    // isn't focusable, it may contain something that is, so let
                    // the root view try to give this focus if nothing else does.
                    if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
                        mParent.focusableViewAvailable(this);
                    }
                }
            }
    
            /* Check if the GONE bit has changed */
            if ((changed & GONE) != 0) {
                needGlobalAttributesUpdate(false);
                requestLayout();
    
                if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
                    if (hasFocus()) {
                        clearFocus();
                        if (mParent instanceof ViewGroup) {
                            ((ViewGroup) mParent).clearFocusedInCluster();
                        }
                    }
                    clearAccessibilityFocus();
                    destroyDrawingCache();
                    if (mParent instanceof View) {
                        // GONE views noop invalidation, so invalidate the parent
                        ((View) mParent).invalidate(true);
                    }
                    // Mark the view drawn to ensure that it gets invalidated properly the next
                    // time it is visible and gets invalidated
                    mPrivateFlags |= PFLAG_DRAWN;
                }
                if (mAttachInfo != null) {
                    mAttachInfo.mViewVisibilityChanged = true;
                }
            }
    
            /* Check if the VISIBLE bit has changed */
            if ((changed & INVISIBLE) != 0) {
                needGlobalAttributesUpdate(false);
                /*
                 * If this view is becoming invisible, set the DRAWN flag so that
                 * the next invalidate() will not be skipped.
                 */
                mPrivateFlags |= PFLAG_DRAWN;
    
                if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
                    // root view becoming invisible shouldn't clear focus and accessibility focus
                    if (getRootView() != this) {
                        if (hasFocus()) {
                            clearFocus();
                            if (mParent instanceof ViewGroup) {
                                ((ViewGroup) mParent).clearFocusedInCluster();
                            }
                        }
                        clearAccessibilityFocus();
                    }
                }
                if (mAttachInfo != null) {
                    mAttachInfo.mViewVisibilityChanged = true;
                }
            }
    
            if ((changed & VISIBILITY_MASK) != 0) {
                // If the view is invisible, cleanup its display list to free up resources
                if (newVisibility != VISIBLE && mAttachInfo != null) {
                    cleanupDraw();
                }
    
                if (mParent instanceof ViewGroup) {
                    ((ViewGroup) mParent).onChildVisibilityChanged(this,
                            (changed & VISIBILITY_MASK), newVisibility);
                    ((View) mParent).invalidate(true);
                } else if (mParent != null) {
                    mParent.invalidateChild(this, null);
                }
    
                if (mAttachInfo != null) {
                    dispatchVisibilityChanged(this, newVisibility);
    
                    // Aggregated visibility changes are dispatched to attached views
                    // in visible windows where the parent is currently shown/drawn
                    // or the parent is not a ViewGroup (and therefore assumed to be a ViewRoot),
                    // discounting clipping or overlapping. This makes it a good place
                    // to change animation states.
                    if (mParent != null && getWindowVisibility() == VISIBLE &&
                            ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
                        dispatchVisibilityAggregated(newVisibility == VISIBLE);
                    }
                    notifySubtreeAccessibilityStateChangedIfNeeded();
                }
            }
    ...
    }
    
        public static final int VISIBLE = 0x00000000;
        public static final int INVISIBLE = 0x00000004;
        public static final int GONE = 0x00000008;
    

    这段代码有点长,省略掉其他无关的代码,newVisibilityflagsVISIBILITY_MASK位与,而VISIBILITY_MASK = 0x0000000C, C对应与1100。 所以flagsVISIBILITY_MASK位与之后,还是原来的值。

    changed是现在的mViewFlagsold进行位或,而现在的mViewFlagsold只有第3位和第4位有区别。

    根据InVisibleGONE的值,可以将第3位理解为控制invisiblie位,而第4位理解为Gone的位。所有如果第3位不一样,那么changed = 4, 如果第4位不一样,changed = 8.

    changed = 4 意味着view有可能发生了如下的变化:

    1. visibility -> invisible
    2. gone -> invisible

    同样 changed = 8, 意味着view 发生了如下的变化:

    1. invisible -> gone
    2. visible -> gone

    这样一分析,后面的代码就好理解多了。

         if (newVisibility == VISIBLE) {
              if ((changed & VISIBILITY_MASK) != 0) {
                    /*
                     * If this view is becoming visible, invalidate it in case it changed while
                     * it was not visible. Marking it drawn ensures that the invalidation will
                     * go through.
                     */
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(true);
    

    这一段说明,如果设置了可见,且之前不是可见,那么必定要invalidate. 而invalidate只会调用onDraw,并不会引起onLayout的调用。

    if ((changed & GONE) != 0) {
      needGlobalAttributesUpdate(false);
      requestLayout();
    

    这一段说明,只要控制Gone位的标志位发生了变化,就一定会引起requestLayout, 而requestLayout则会重新调用performTraversals(),自然会引起ViewTreeObserval.OnGlobalLayoutListener的回调。这也不难理解,因为View.Gone说明该view不会占用屏幕空间,那么GONE位的变化,必定有屏幕的布局发生变化。

      if ((changed & INVISIBLE) != 0) {
      ...
      }
    

    这一段说明,如果invisible发生变化,则没有调用requestLayout,而从gone->invisible这一种变化,上一段的逻辑已经包含到了。所以这一段if逻辑没有requestLayout也是情理之中。

           if ((changed & VISIBILITY_MASK) != 0) {
                // If the view is invisible, cleanup its display list to free up resources
                if (newVisibility != VISIBLE && mAttachInfo != null) {
                    cleanupDraw();
                }
    
                if (mParent instanceof ViewGroup) {
                    ((ViewGroup) mParent).onChildVisibilityChanged(this,
                            (changed & VISIBILITY_MASK), newVisibility);
                    ((View) mParent).invalidate(true);
                } else if (mParent != null) {
                    mParent.invalidateChild(this, null);
                }
    ...
    }
    
    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
            if (mTransition != null) {
                if (newVisibility == VISIBLE) {
                    mTransition.showChild(this, child, oldVisibility);
                } else {
                    mTransition.hideChild(this, child, newVisibility);
                    if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
                        // Only track this on disappearing views - appearing views are already visible
                        // and don't need special handling during drawChild()
                        if (mVisibilityChangingChildren == null) {
                            mVisibilityChangingChildren = new ArrayList<View>();
                        }
                        mVisibilityChangingChildren.add(child);
                        addDisappearingView(child);
                    }
                }
            }
    

    这一段是控制其子view的变化,如果newVisibility == VISIBLE, 那么最终会调用到mTransition.showChild()

    public void showChild(ViewGroup parent, View child, int oldVisibility) {
            addChild(parent, child, oldVisibility == View.GONE);
     }
    

    而其他,则会调用hideChild()

    public void hideChild(ViewGroup parent, View child, int newVisibility) {
           removeChild(parent, child, newVisibility == View.GONE);
       }
    

    所以总结如下:

    1. visible-> invisible, 只会引起invalidate, 不会引起requestLayout, 即不会引起布局变化
    2. xx -> Gone 或者Gone -> xx, 则会引起requestLayout, 即引起布局变化。

    三. 实验验证

    分别写了两个自定义view, 一个为继承自TextView, 一个自定义继承自LinearLayout
    1. visible-> invisible

    visible->invisible.png

    2. visible->gone

    visible->gone.png

    3. invisible->visible

    invisible->visible.png

    4. invisible->gone

    invisible->gone.png

    5. gone->visible

    gone->visible.png

    6. gone->invisible

    gone->invisible.png

    相关文章

      网友评论

          本文标题:View的可见性和绘制流程

          本文链接:https://www.haomeiwen.com/subject/wcwkoftx.html