在Android开发过程中,经常需要获取Window或某个View的可见性变化时机,以便在View的Visibility变化时进行相应的处理。目前,比较常用的判断View可见性时机的回调有

  • onWindowVisibilityChanged
  • onVisibilityChanged
  • OnAttachStateChangeListener#onViewAttachedToWindow

一、onWindowVisibilityChanged

         /*** Called when the window containing has change its visibility* (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}).  Note* that this tells you whether or not your window is being made visible* to the window manager; this does <em>not</em> tell you whether or not* your window is obscured by other windows on the screen, even if it* is itself visible.** @param visibility The new visibility of the window.*/protected void onWindowVisibilityChanged(@Visibility int visibility) {if (visibility == VISIBLE) {initialAwakenScrollBars();}}

由方法注释可知,它是在窗口可见性改变时调用,而且注意这只是在Window对WindowManager可见时调用,并不是告知你当前可见的Window是否被遮挡。

查看代码,发现其调用位置有3处,添加时在performTraversals方法中(代码有省略),Activity onStop生命周期会removeDecorView和对应的Window,在removeView方法中会调用dispatchDetachedFromWindow方法,该方法内又会调用onWindowVisibilityChanged

private void performTraversals() {// cache mView since it is used so much below...final View host = mView;......final int viewVisibility = getHostVisibility();final boolean viewVisibilityChanged = !mFirst&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded// Also check for possible double visibility update, which will make current// viewVisibility value equal to mViewVisibility and we may miss it.|| mAppVisibilityChanged);mAppVisibilityChanged = false;final boolean viewUserVisibilityChanged = !mFirst &&((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));......if (mFirst) {......// We used to use the following condition to choose 32 bits drawing caches:// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888// However, windows are now always 32 bits by default, so choose 32 bitsmAttachInfo.mUse32BitDrawingCache = true;mAttachInfo.mWindowVisibility = viewVisibility;mAttachInfo.mRecomputeGlobalAttributes = false;mLastConfigurationFromResources.setTo(config);mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;// Set the layout direction if it has not been set before (inherit is the default)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) {// After making a window gone, we will count it as being// shown for the first time the next time it gets focus.mHasHadWindowFocus = false;}}}
void dispatchDetachedFromWindow() {AttachInfo info = mAttachInfo;if (info != null) {int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(GONE);// ③if (isShown()) {// Invoking onVisibilityAggregated directly here since the subtree// will also receive detached from windowonVisibilityAggregated(false);}}}onDetachedFromWindow();onDetachedFromWindowInternal();......ListenerInfo li = mListenerInfo;final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =li != null ? li.mOnAttachStateChangeListeners : null;if (listeners != null && listeners.size() > 0) {// 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.for (OnAttachStateChangeListener listener : listeners) {listener.onViewDetachedFromWindow(this);}}......}

调用位置已注释,首先看第一处①,应用启动时,host就是DecorView对象,它是一个ViewGroup,所以在ViewGroup类中查看

     /*** ViewGroup.class*/@Override@UnsupportedAppUsagevoid dispatchAttachedToWindow(AttachInfo info, int visibility) {mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;// [1]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];// [2]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;......// Transfer all pending runnables.if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}performCollectViewAttributes(mAttachInfo, visibility);onAttachedToWindow();// [1]ListenerInfo li = mListenerInfo;final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =li != null ? li.mOnAttachStateChangeListeners : null;if (listeners != null && listeners.size() > 0) {// 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.for (OnAttachStateChangeListener listener : listeners) {listener.onViewAttachedToWindow(this);// [2]}}int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(vis);// [3]if (isShown()) {// Calling onVisibilityAggregated directly here since the subtree will also// receive dispatchAttachedToWindow and this same callonVisibilityAggregated(vis == VISIBLE);}}// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.// As all views in the subtree will already receive dispatchAttachedToWindow// traversing the subtree again here is not desired.onVisibilityChanged(this, visibility);......}

View#dispatchAttachedToWindow方法集中处理了onAttachedToWindowonViewAttachedToWindowonWindowVisibilityChangedonVisibilityChanged这4种可见性变化相关的回调函数。

对于onWindowVisibilityChanged方法来说,首先会通过AttachInfo对象获取现在窗口(mWindowVisibility)可见性。mWindowVisibility变量的赋值也在performTraversals方法中。

......
final int viewVisibility = getHostVisibility();
// mViewVisibility在创建ViewRootImpl对象时,初始化值是GONE;在完成测量布局后赋值为viewVisibility
final boolean viewVisibilityChanged = !mFirst&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded// Also check for possible double visibility update, which will make current// viewVisibility value equal to mViewVisibility and we may miss it.|| mAppVisibilityChanged);
......
mAttachInfo.mWindowVisibility = viewVisibility;
......

在Window被添加到屏幕上后(mWindowSession.addToDisplay),getHostVisibility()就返回Visible。所以只要mWindowVisibility不为GONE就会调用onWindowVisibilityChanged方法。这就是它的第一种调用场景。
查看*getHostVisibility()*方法

// #ViewRootImpl.java
int getHostVisibility() {return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;}

可见,最终取得的是mView对象的可见性,而mView对象就是DecorView对象(在ViewrootImpl#setView方法中设置的),所以:

mWindowVisibility方法只会在页面(Activity和Dialog)打开和关闭(具体说是ActivityDialogWindow可见性改变时)各调用一次

第二处调用位置在②处,主要代码是

 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)。

 // ViewRootImpl.classprivate void performTraversals() {......host.dispatchAttachedToWindow(mAttachInfo, 0);......}// View.classvoid dispatchAttachedToWindow(AttachInfo info, int visibility) {......// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.// As all views in the subtree will already receive dispatchAttachedToWindow// traversing the subtree again here is not desired.onVisibilityChanged(this, visibility);......}

然后,每次调用setVisibility方法来控制视图的可见性时都会回调该方法。

 // View.classpublic 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方法

// ActivityThread.class
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,此时mVisibleFromServertrue,进入条件体,首先将mVisibleFromServer设置为false,然后通过DecorView调用setVisibility方法来控制视图显示,并默认传输View.INVISIBLE。我们知道调用setVisibility就可能触发onVisibilityChanged的执行。

总结

  • 页面加载时,会在View attachWindow时(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) {// 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.for (OnAttachStateChangeListener listener : listeners) {listener.onViewAttachedToWindow(this);}}......}

Android View可见性判断方法相关推荐

  1. android view可见性监听,view 可见性 监听探究

    view 可见性监听 今天产品有个需求,当一个view任何又不可见->k可见时,上报这个view的特定信息.任何由不可见->可见,包括进入一个页面:从其他页面返回到该页面:在页面内view ...

  2. android view可见性监听,Android检测View的可见性

    Android中我们经常会用到判断View的可见行,当然有人会说View.VISIBLE就可以了,但是有时候这个真是满足不了,有时候我们为了优化,在View滚到得不可见的时候或者由于滚到只显示了部分内 ...

  3. Android自定义View的实现方法,带你一步步深入了解View(四)

    不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析.视图的绘制流程.视图的状态及重绘等知识,算是把View中很多重要的知识 ...

  4. Android之如何判断当前是阿拉伯布局的方法

    1 问题 判断当前是不是阿拉伯布局的方法 2 几种判断方法 @SuppressLint("NewApi")public static boolean isLayoutRtl(Vie ...

  5. android view.isshown,源码解析view的显示判断用isShown()还是View.VISIBLE

    前言 平时我们对View的显示判断都是用简要的方式去判断,那么,究竟是用view.isShown()去判断还是用view. getVisibility() == View.VISIBLE 判断好呢?其 ...

  6. android 虚方法,尝试在空对象引用上调用虚方法’android.view.View android.view.View.getRootView()’...

    我收到这个错误, "Attempt to invoke virtual method 'android.view.View android.view.View.getRootView()' ...

  7. import android.view.window;,尝试在空对象引用上调用虚拟方法‘android.view.Window$回调...

    尝试在空对象引用上调用虚拟方法'android.view.Window$回调-android.view.Window.getCallback()' 当我SplashActivity打开LoginAct ...

  8. android view超出屏幕_Android APP界面保持屏幕常亮方法

    阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 1. 在AndroidMainfest.xml 中申请 WAKE_LOCK 唤醒锁权限 <?xml version=" ...

  9. android view使用方法,android – 如何使用getView()方法,它在哪里被调用?

    我是Android开发的新手,并且一直遵循Android网站上提供的教程.我目前在视图教程部分,特别是Grid Views: Hello, Grid View Tutorial的教程. 我无法通过适配 ...

最新文章

  1. 看漫画学python pdf下载_用python下载漫画并打包成pdf文件
  2. python处理excel文件-python读取excel文件
  3. 关于this的指向问题
  4. 好好学python·函数进阶(递归函数,回调函数,闭包函数,匿名函数,迭代器)
  5. 深度学习总结:GAN,原理,算法描述,pytoch实现
  6. [数据结构] - ArrayList探究
  7. Windows重装Anaconda3失败解决方案【重装失败10来次首次成功的案例!】
  8. sqoop的安装和使用
  9. hadoop 依赖式job_每天一学:一个轻量级分布式任务调度框架 XXL-JOB
  10. Java中提取字符串中的数字
  11. 更复杂的缓存穿透怎么解决
  12. 直播电商源码,无加密
  13. 25G差分信号对内等长相差太大怎么处理?在哪里绕好一点
  14. ios 旋转屏幕试图切换_iOS播放器横竖屏切换的实现
  15. k线形态python_Python量化分析之K线模式识别
  16. a标签href属性的用法
  17. 波浪动力滑翔机的综述
  18. python打造最全画地图,可视化数据
  19. 验证(Verification) 确认(Validation)鉴定( Qualification) 的区别
  20. ValueError: Please provide model inputs as a list or tuple of 2 or 3 elements: (input, target)

热门文章

  1. 小米8计算机快捷键,小米8这4个隐藏功能,使用半年今天才意外发现
  2. 红外接收过程程序详解
  3. RK3399平台开发系列讲解(内核驱动外设篇)6.3、RK3399平台增加红外接收功能
  4. Ubuntu22 Docker运行SRS流媒体服务,推拉流,yolov5训练自定义模型进行视频流识别
  5. 虚拟机导入OVF格式
  6. 2021-07-08IDEA+Java+Servlet+JSP+Bootstrap+Mysql实现Web学生成绩管理系统
  7. CCNP---重发布
  8. 设计模式之策略模式+工厂模式+模板模式结合
  9. 【马蜂窝 加速乐cookie】一次坑爹的获取html源代码不到之路
  10. 2、金融平台系统软件整体架构浅谈