View的可见性和绘制流程

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 listeners = mOnGlobalLayoutListeners;
        if (listeners != null && listeners.size() > 0) {
            CopyOnWriteArray.Access 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();
                    }
                    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的可见性和绘制流程)