在Android开发过程中,经常需要获取Window或某个View的可见性变化时机,以便在View的Visibility变化时进行相应的处理。目前,比较常用的判断View可见性时机的回调有
- onWindowVisibilityChanged
- onVisibilityChanged
- OnAttachStateChangeListener#onViewAttachedToWindow
一、onWindowVisibilityChanged
protected void onWindowVisibilityChanged(@Visibility int visibility) {
if (visibility == VISIBLE) {
initialAwakenScrollBars();
}
}
由方法注释可知,它是在窗口可见性改变时调用,而且注意这只是在Window对WindowManager可见时调用,并不是告知你当前可见的Window是否被遮挡。
查看代码,发现其调用位置有3处,添加时在performTraversals 方法中(代码有省略),Activity onStop生命周期会remove掉DecorView和对应的Window,在removeView 方法中会调用dispatchDetachedFromWindow 方法,该方法内又会调用onWindowVisibilityChanged
private void performTraversals() {
final View host = mView;
......
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
......
if (mFirst) {
......
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {
......
}
}
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
mHasHadWindowFocus = false;
}
}
}
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
if (isShown()) {
onVisibilityAggregated(false);
}
}
}
onDetachedFromWindow();
onDetachedFromWindowInternal();
......
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewDetachedFromWindow(this);
}
}
......
}
调用位置已注释,首先看第一处①,应用启动时,host就是DecorView对象,它是一个ViewGroup,所以在ViewGroup类中查看
@Override
@UnsupportedAppUsage
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
可以看到,ViewGroup#dispatchAttachedToWindow 方法主要作用就是
[1] 调用自身的dispatchAttachedToWindow方法,处理自己attach到Window
[2] 向子View分发事件,让每个字View处理attach到Window的事件
由此可知,最终都会调用到View#dispatchAttachedToWindow 方法
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
......
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
onVisibilityAggregated(vis == VISIBLE);
}
}
onVisibilityChanged(this, visibility);
......
}
View#dispatchAttachedToWindow 方法集中处理了onAttachedToWindow 、onViewAttachedToWindow 、onWindowVisibilityChanged 和onVisibilityChanged 这4种可见性变化相关的回调函数。
对于onWindowVisibilityChanged 方法来说,首先会通过AttachInfo对象获取现在窗口(mWindowVisibility)可见性。mWindowVisibility 变量的赋值也在performTraversals方法中。
......
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
|| mAppVisibilityChanged);
......
mAttachInfo.mWindowVisibility = viewVisibility;
......
在Window被添加到屏幕上后(mWindowSession.addToDisplay),getHostVisibility() 就返回Visible 。所以只要mWindowVisibility 不为GONE 就会调用onWindowVisibilityChanged 方法。这就是它的第一种调用场景。
第二处调用位置在②处,主要代码是
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
......
}
在DecorView加载时,如果mFirst==false (非首次加载),很可能进行该条件体进行调用。
第三种情况③,例如打开一个新页面,老页面走到onStop声明周期方法,如③处,只要不是GONE就会调用。
综上,onWindowVisibilityChanged 的调用:
- 每当一个页面打开或移除时,如果关联的Window可见(不等于GONE),则会调用
- 打开时,是在
View#dispatchAttachedToWindow 中进行调用,分离时在View#dispatchDetachFromWindow 时调用,并传入默认参数GONE
二、onVisibilityChanged
onVisibilityChanged 调用时机和onWindowVisibilityChanged 非常类似,对于APP启动打开页面时,会处理重写该方法的View attach到Window的事件,此时默认传入的参数是Visible(值为0)。
private void performTraversals() {
......
host.dispatchAttachedToWindow(mAttachInfo, 0);
......
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
......
onVisibilityChanged(this, visibility);
......
}
然后,每次调用setVisibility 方法来控制视图的可见性时都会回调该方法。
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
void setFlags(int flags, int mask) {
......
if ((changed & VISIBILITY_MASK) != 0) {
......
if (mAttachInfo != null) {
dispatchVisibilityChanged(this, newVisibility);
......
}
}
.......
}
protected void dispatchVisibilityChanged(@NonNull View changedView,
@Visibility int visibility) {
onVisibilityChanged(changedView, visibility);
}
同样,在页面关闭时(或打开新页面覆盖旧页面),会执行onStop生命周期方法,其实是调用ActivityThread#handleStopActivity 方法,然后会调用到updateVisibility 方法
private void updateVisibility(ActivityClientRecord r, boolean show) {
View v = r.activity.mDecor;
if (v != null) {
if (show) {
if (!r.activity.mVisibleFromServer) {
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
......
} else {
if (r.activity.mVisibleFromServer) {
r.activity.mVisibleFromServer = false;
mNumVisibleActivities--;
v.setVisibility(View.INVISIBLE);
}
}
}
}
mVisibleFromServer 默认是false,在onResume后赋值为true,此时mVisibleFromServer 为true,进入条件体,首先将mVisibleFromServer 设置为false,然后通过DecorView调用setVisibility 方法来控制视图显示,并默认传输View.INVISIBLE 。我们知道调用setVisibility 就可能触发onVisibilityChanged 的执行。
总结:
- 页面加载时,会在View attach到Window时(dispatchAttachedToWindow方法)调用
onVisibilityChanged - 通过
setVisibility 来改变View的可见性时会调用onVisibilityChanged - 关闭页面时,在
handleStopActivity 方法中会调用updateVisibility 方法,内部也会调用setVisibility
,并传入默认参数INVISIBLE。
三、OnAttachStateChangeListener#onViewAttachedToWindow
OnAttachStateChangeListener定义了2个接口方法,分别在View Attach/Detach to Window时候调用。要使用该接口首先需要注册这个监听器。
public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
ListenerInfo li = getListenerInfo();
if (li.mOnAttachStateChangeListeners == null) {
li.mOnAttachStateChangeListeners
= new CopyOnWriteArrayList<OnAttachStateChangeListener>();
}
li.mOnAttachStateChangeListeners.add(listener);
}
调用位置很单纯,就在View#dispatchAttachedToWindow方法里,如果有注册过该监听器,就会调用
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
......
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
......
}
|