view visibility

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);
    }
    
  • 然后在ViewVisibilityObserveronAttachedToWindow()/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)
    }
}