源码SDK: 24
最近碰到一个问题,某一个Fragment里,有一个View上有一个动画,而当Fragment不可见的时候(切换到别的Fragment)。这时候,View的动画应该停止,以便节省主线程计算动画值,所以研究了View的onVisibilityChanged方法,在View不可见的时候关闭动画。
研究onVisibilityChanged方法前,需要了解onAttachedToWindow,该方法是View添加到了Window窗口之后的一个回调。对应的是从Window窗口移除的时候回调onDetachedFromWindow。
上图中View需要添加到一个已经存在的试图树中,左边的试图树是一个已经完成过首次绘制的试图树。右边的是一个未完成首次绘制的试图树(比如Activity在onCreate里通过setContentView设置了布局,但是Activity只有onResume之后才会执行首次绘制)。
一个View是否attach到window上,framework是通过判断View中的mAttachInfo成员属性来判断的,也就是说整个试图树执行首次绘制的时候,ViewRootImpl会将自己的内部的mAttachInfo分发到所有的视图节点中。具体是通过调用View中的dispatchAttachedToWindow方法来下发。
android.view.View.java
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// .....略
}
View一旦收到AttachInfo对象后,也就代表View被attach到了window。
什么是首次绘制?
ViewRootImpl创建了,第一次执行performTravels方法。
android.view.ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
// ...
mFirst = true; // true for the first time the view is added
// ...
}
private void performTraversals() {
final View host = mView; // 根节点(一般是decorView)
// ...
if (mFirst) {
// ...
host.dispatchAttachedToWindow(mAttachInfo, 0);
// ...
}
// ...
mFirst = false;
}
既然View是通过mAttachInfo属性来判断是否已经添加到window,那么如果一个View被添加到一个ViewGroup,而这个ViewGroup未被添加到window,当然这个View也不会收到attachToWindow的回调。当这个ViewGroup被添加到window之后,View才有可能收到attachToWindow回调。
android.view.ViewGroup.java
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
}
}
android.view.View.java
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
onAttachedToWindow(); // 在这里- -
}
onAttachedToWindow调用时机
onDetachedFromWindow调用时机
onVisibilityChanged是否调用,依赖于View是否执行过onAttachedToWindow方法。也就是View是否被添加到Window上。
正常来说,调用View的setVisibility(@Visibility int visibility)方法,只要传入的visibility和当前View的visibility属性不一致就会触发onVisibilityChanged方法的调用。但是如果onAttachedToWindow方法未调用的情况下(View的mAttachInfo为空),是不会调用onVisibilityChanged的。
android.view.View.java
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
void setFlags(int flags, int mask) {
if ((changed & VISIBILITY_MASK) != 0) {
// 只有曾经执行过onAttachedToWindow方法才能执行dispatchVisibilityChanged方法
if (mAttachInfo != null) {
dispatchVisibilityChanged(this, newVisibility);
}
}
}
protected void dispatchVisibilityChanged(@NonNull View changedView,
@Visibility int visibility) {
// 对View来说,执行dispatchVisibilityChanged方法就是调用onVisibilityChanged方法
onVisibilityChanged(changedView, visibility);
}
当View的祖先节点(父亲的父亲的…父亲),的visibility改变之后,ViewGroup会通过dispatchVisibilityChanged传递给所有的子View该事件(因为如果祖先不可见,子孙自然不可见…)。
比如,当Activity执行onStop生命周期的时候,所有的View节点都会受到onVisibilityChanged生命周期,此时的visibility为INVISIBLE,changedView为Activity的根节点,也就是DecorView。调用栈如下:
at test.com.myapplication.MainActivity$1.onVisibilityChanged(MainActivity.java:439)
at android.view.View.dispatchVisibilityChanged(View.java:10308)
at android.view.ViewGroup.dispatchVisibilityChanged(ViewGroup.java:1280)
at android.view.ViewGroup.dispatchVisibilityChanged(ViewGroup.java:1280)
at android.view.ViewGroup.dispatchVisibilityChanged(ViewGroup.java:1280)
at android.view.ViewGroup.dispatchVisibilityChanged(ViewGroup.java:1280)
at android.view.ViewGroup.dispatchVisibilityChanged(ViewGroup.java:1280)
at android.view.View.setFlags(View.java:11533)
at android.view.View.setVisibility(View.java:8069)
at android.app.ActivityThread.updateVisibility(ActivityThread.java:3902)
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:3922)
at android.app.ActivityThread.-wrap25(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1512)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6121)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
android.view.ViewGroup.java
protected void dispatchVisibilityChanged(View changedView, int visibility) {
super.dispatchVisibilityChanged(changedView, visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchVisibilityChanged(changedView, visibility);
}
}
但是,祖先如果从invisible设置成visible,子View虽然会收到onVisibilityChanged回调,但是子View可能自身就是invisible,所以仍然不可见,并不是祖先可见子孙就可见
也就是说View即使是inVisible或者gone,也能收到onVisibilityChanged回调。
当View或者View的祖先节点被添加到window上时,View的dispatchAttachedToWindow方法将会被调用,在View的dispatchAttachedToWindow方法里,将会调用View的onVisibilityChanged方法。