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