View的hide
埋点缺失
通常针对一个View
进行埋点时,一般会有show/hide
两个埋点,分别对应于View
的展现和消失。
而在View
的生命周期中,其onAttachedToWindow()
回调方法表示View
即将展现;与之相对的onDetachedFromWindow()
回调方法则表示View
即将消失。所以,一般针对show/hide
两个埋点,直接在onAttachedToWindow()
和onDetachedFromWindow()
中埋点即可。
然而当我们打开一个新的Activity
页面时,我们会发现此时View
已经看不到了,但是由于其onDetachedFromWindow()
方法没有被系统回调,所以我们只有show
埋点,而没有hide
埋点,那么问题就来了,如果让show/hide
这两个埋点就对应上呢?
View的生命周期
对于一个View
来说,其本身涉及的回调函数有很多,在此我们只关注与View
的展现/消失相关的回调函数,为了方便描述,我们将该类函数统称为View
的生命周期函数。其主要包括如下函数:
// View被添加到Window时被调用,View开始绘制。
// View是可见的。
protected void onAttachedToWindow() {}
// View所属于的Window展现/消失时被调用。
// View的展现/消失由visibility决定。
protected void onWindowVisibilityChanged(int visibility) {}
// View或其祖先View的visibility发生改变时被调用。
// View的展现/消失由visibility决定。
// 注意: 滑动RecyclerView,当一个item view消失时该方法不会被调用。
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {}
// View从Window移除时被调用,View不再绘制。
// View不可见。
protected void onDetachedFromWindow() {}
通过以上方法,我们就可以知道一个View
的可见状态是显示还是隐藏了。
获取View的可见状态
获取View
的可见状态整体流程如下:
LifecycleView -> ViewLifecycle -> ViewVisibilityObserver -> OnViewVisibilityChangedListener
首先通过重写
View
的上述生命周期函数来监听View
的状态并将其分发出去。public class LifecycleView extends View implements ViewLifecycleOwner { @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); mViewLifecycleRegistry.onWindowVisibilityChanged(this, visibility); } @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); mViewLifecycleRegistry.onVisibilityChanged(this, changedView, visibility); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mViewLifecycleRegistry.onAttachedToWindow(this); } @Override protected void onDetachedFromWindow() { mViewLifecycleRegistry.onDetachedFromWindow(this); super.onDetachedFromWindow(); } @Override public ViewLifecycle getViewLifecycle() { return mViewLifecycleRegistry; } private final ViewLifecycleRegistry mViewLifecycleRegistry = new ViewLifecycleRegistry(this); }
然后在
ViewVisibilityObserver
的onAttachedToWindow()/onDetachedFromWindow()
方法中对View设置正确的visibility
,同时在setVisibility()
中过滤掉重复的状态,避免多次调用。public final class ViewVisibilityObserver implements ViewLifecycleObserver { private static final String TAG = "ViewVisibilityObserver"; private final List<OnViewVisibilityChangedListener> mOnViewVisibilityChangedListener = new ArrayList<>(); private int mViewVisibility = ViewLifecycle.VIEW_VISIBILITY_UNKNOWN; @Override public void onWindowVisibilityChanged(View thisView, int visibility) { setVisibility(thisView, visibility); } @Override public void onVisibilityChanged(View thisView, @NonNull View changedView, int visibility) { setVisibility(thisView, visibility); } @Override public void onAttachedToWindow(View thisView) { setVisibility(thisView, ViewLifecycle.VIEW_VISIBILITY_VISIBLE); } @Override public void onDetachedFromWindow(View thisView) { setVisibility(thisView, ViewLifecycle.VIEW_VISIBILITY_GONE); } private void setVisibility(View thisView, int visibility) { if (visibility == ViewLifecycle.VIEW_VISIBILITY_UNKNOWN) { return; } if (visibility == ViewLifecycle.VIEW_VISIBILITY_INVISIBLE) { visibility = ViewLifecycle.VIEW_VISIBILITY_GONE; } if (mViewVisibility == visibility) { return; } mViewVisibility = visibility; TLog.e(TAG, thisView + " visibility=" + ViewLifecycleUtil.resolveVisibilityName(visibility)); for (OnViewVisibilityChangedListener listener : mOnViewVisibilityChangedListener) { listener.onViewVisibilityChanged(thisView, visibility); } } }
最后通过添加
OnViewVisibilityChangedListener
来监听View
的可见性状态。public interface OnViewVisibilityChangedListener { /** * our custom method and filter duplicate View# visibility event; * * @param thisView * @param visibility */ void onViewVisibilityChanged(View thisView, @ViewLifecycle.ViewVisibility int visibility); }
如何使用?
我们可以在需要获取View
状态的容器中添加一个和容器大小相同的LifecycleView
,然后在添加监听器即可。
LifecycleView lifecycleView = new LifecycleView(context);
lifecycleView.setId(R.id.view_lifecycle_id);
container.addView(lifecycleView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
final ViewVisibilityObserver viewVisibilityObserver = new ViewVisibilityObserver();
lifecycleView.getViewLifecycle().addViewLifecycleObserver(viewVisibilityObserver);
viewVisibilityObserver.addOnViewVisibilityChangedListener((thisView, visibility) -> {
TLog.DEBUG("ViewLifecycleListener", "onVisibilityChanged; visibility=" + ViewLifecycleUtil.resolveVisibilityName(visibility));
});
当在RecyclerView中使用时,建议将ViewVisibilityObserver
定义在ViewHolder
中,然后在onBindViewHolder()
方法中添加/移除OnViewVisibilityChangedListener
,代码如下:
class AAdapter : RecyclerView.Adapter<NameViewHolder>() {
override fun onBindViewHolder(holder: NameViewHolder, position: Int) {
TLog.e(ViewLifecycleRegistry.TAG, "onBindViewHolder; position=$position")
holder.viewVisibilityObserver.apply {
clearAllOnViewVisibilityChangedListener()
addOnViewVisibilityChangedListener { thisView, visibility ->
TLog.e(ViewLifecycleRegistry.TAG, "onBindViewHolder; item at position=${holder.adapterPosition} ${ViewLifecycleUtil.resolveVisibilityName(visibility)}")
}
}
}
}
class NameViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val nameView: TextView = itemView.findViewById(android.R.id.text1)
val viewVisibilityObserver: ViewVisibilityObserver = ViewVisibilityObserver()
init {
val viewLifecycleOwner : ViewLifecycleOwner = when(itemView) {
is ViewLifecycleOwner -> {
itemView
}
is ViewGroup -> {
val lifecycleView = LifecycleView(itemView.context)
lifecycleView.id = R.id.view_lifecycle_id
itemView.addView(lifecycleView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
lifecycleView
}
else -> {
val lifecycleFrameLayout = LifecycleFrameLayout(itemView.context)
lifecycleFrameLayout.id = R.id.view_lifecycle_id
lifecycleFrameLayout.addView(itemView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
lifecycleFrameLayout
}
}
viewLifecycleOwner.viewLifecycle.addViewLifecycleObserver(viewVisibilityObserver)
}
}