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()
中调用
如果标志位didLayout
为true
,那么会调用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;
这段代码有点长,省略掉其他无关的代码,newVisibility
是 flags
与VISIBILITY_MASK
位与,而VISIBILITY_MASK = 0x0000000C
, C
对应与1100
。 所以flags
与VISIBILITY_MASK
位与之后,还是原来的值。
而changed
是现在的mViewFlags
与old
进行位或,而现在的mViewFlags
与old
只有第3位和第4位有区别。
根据InVisible
和GONE
的值,可以将第3位理解为控制invisiblie
位,而第4位理解为Gone
的位。所有如果第3位不一样,那么changed = 4
, 如果第4位不一样,changed = 8
.
而changed = 4
意味着view
有可能发生了如下的变化:
-
visibility
->invisible
-
gone
->invisible
同样 changed = 8
, 意味着view
发生了如下的变化:
-
invisible
->gone
-
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);
}
所以总结如下:
-
visible
->invisible
, 只会引起invalidate
, 不会引起requestLayout
, 即不会引起布局变化 -
xx
->Gone
或者Gone
->xx
, 则会引起requestLayout
, 即引起布局变化。
三. 实验验证
分别写了两个自定义view
, 一个为继承自TextView
, 一个自定义继承自LinearLayout
。
1. visible-> invisible
2. visible->gone
3. invisible->visible
4. invisible->gone
5. gone->visible
6. gone->invisible