Android View onVisibilityChanged onAttachedToWindow onDetachedFromWindow

源码SDK: 24

简介

最近碰到一个问题,某一个Fragment里,有一个View上有一个动画,而当Fragment不可见的时候(切换到别的Fragment)。这时候,View的动画应该停止,以便节省主线程计算动画值,所以研究了View的onVisibilityChanged方法,在View不可见的时候关闭动画。

onAttachedToWindow与onDetachedFromWindow

研究onVisibilityChanged方法前,需要了解onAttachedToWindow,该方法是View添加到了Window窗口之后的一个回调。对应的是从Window窗口移除的时候回调onDetachedFromWindow。

何时触发attached与detached

Android View onVisibilityChanged onAttachedToWindow onDetachedFromWindow_第1张图片

上图中View需要添加到一个已经存在的试图树中,左边的试图树是一个已经完成过首次绘制的试图树。右边的是一个未完成首次绘制的试图树(比如Activity在onCreate里通过setContentView设置了布局,但是Activity只有onResume之后才会执行首次绘制)。

  1. 当执行addView方法之后,左边的View会回调onAttachedToWindow,而右边的不会回调onAttachedToWindow。
  2. 右边的View,在试图树执行首次绘制的时候,会回调其 onAttachedToWindow方法。

一个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调用时机

  1. View添加到一个已经绘制过的试图树上,立马触发回调。
  2. View添加到一个没被绘制过的试图树上,等试图树首次绘制的时候,收到回调。
  3. View添加到一个ViewGroup上,但是该ViewGroup所在的试图树没有被添加到window上,不会回调。等待ViewGroup被执行情况1和情况2的时候,View也会收到回调。

onDetachedFromWindow调用时机

  1. View从一个ViewGroup上移除的时候,View曾经执行过onAttachedToWindow方法的话,会回调onDetachedFromWindow方法。
  2. View的祖宗节点(父亲或者父亲的父亲的父亲…节点)从试图树移除的时候,如果View曾经执行过onAttachedToWindow方法,会回调。
  3. 当window从WindowManager上移除的时候(比如activity执行onDestroy方法之后)。该window下的所有的试图节点都会收到onDetachedFromWindow回调。

onVisibilityChanged

onVisibilityChanged是否调用,依赖于View是否执行过onAttachedToWindow方法。也就是View是否被添加到Window上。

onVisibilityChanged被调用时机一

正常来说,调用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);
}

onVisibilityChanged被调用时机二

当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回调。

onVisibilityChanged被调用时机三

当View或者View的祖先节点被添加到window上时,View的dispatchAttachedToWindow方法将会被调用,在View的dispatchAttachedToWindow方法里,将会调用View的onVisibilityChanged方法。

总结

  1. onAttachedToWindow被调用,即代表着View被添加到了一个绘制过的视图树中。
  2. onAttachedToWindow和onDetachedFromWindow可以被调用多次。
  3. 当View被添加到已经绘制过的视图树上时,onAttachedToWindow会被立即执行,接着onVisibilityChanged也会立即执行。
  4. 当View从视图上移除时,如果onAttachedToWindow方法曾经执行过,那么onDetachedFromWindow将会被执行。
  5. onVisibilityChanged被调用的前提是View执行过onAttachedToWindow方法。
  6. 判断View是否执行过onAttachedToWindow的依据是View里的mAttachInfo对象不为空。

你可能感兴趣的:(Android学习)