专栏首页逮虾户View的有效曝光监控(下)|ScrollView NestScrollView篇

View的有效曝光监控(下)|ScrollView NestScrollView篇

各位大佬,能不能随便给我的项目或者之前的文章点个star,苦兮兮。github.com/ 掘金文章

面试官:老哥那么我们继续探讨下这个问题啊。Scrollview和NestScrollView怎么监控呢。

我:???黑人老哥又特么来了。

分析问题

还是和上篇文章一样,我们先看下要解决哪些问题。

  1. ScrollView NestScrollView 的滑动监控怎么做。
  2. View有没有像RecyclerView一样的attach和detch方法,超过1.5s的曝光时间。
  3. View出现一半。

滑动监控

一般人肯定告诉你,这个你自定义个scrollview,然后在onScrollChanged实现个滑动监听的回调什么的。不好意思,我偏不,带你看看另外一个神奇的方法。

先给大家介绍下ViewTreeObserver里面所包含的一些接口。

内部类接口

备注

ViewTreeObserver.OnPreDrawListener

当视图树将要被绘制时,会调用的接口

ViewTreeObserver.OnGlobalLayoutListener

当视图树的布局发生改变或者View在视图树的可见状态发生改变时会调用的接口

ViewTreeObserver.OnGlobalFocusChangeListener

当一个视图树的焦点状态改变时,会调用的接口

ViewTreeObserver.OnScrollChangedListener

当视图树的一些组件发生滚动时会调用的接口

ViewTreeObserver.OnTouchModeChangeListener

当视图树的触摸模式发生改变时,会调用的接口格

各位老哥有没有发现一些奇怪的东西混在里面,哈哈哈。

惯例分析下源码

理论上来说,所有视图状态之类的都是和ViewRootImp相关的。特别是ViewTreeObserver相关的,所以我们的源码分析也是从ViewRootImp开始的。

class ViewRootImp {
    // 根视图绘制
    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return false;
        }

        if (DEBUG_FPS) {
            trackFPS();
        }

        if (!sFirstDrawComplete) {
            synchronized (sFirstDrawHandlers) {
                sFirstDrawComplete = true;
                final int count = sFirstDrawHandlers.size();
                for (int i = 0; i< count; i++) {
                    mHandler.post(sFirstDrawHandlers.get(i));
                }
            }
        }

        scrollToRectOrFocus(null, false);

        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            // 调用viewtree的滑动监听
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        }
        .....
        return useAsyncReport;
    }

}
复制代码

上面的代码可以看出,当mAttachInfo.mViewScrollChanged这个状态位被设置成true的情况下,就会通知viewTree调用滑动监听了。 那么我们的切入点就很简单了,什么时候谁把这个值设置成ture了,是不是就会触发滑动监听了呢。

class View {
     final static class AttachInfo {
         /**
         * Set to true if a view has been scrolled.
         */
        @UnsupportedAppUsage
        boolean mViewScrollChanged;
     }
     /**
     * This is called in response to an internal scroll in this view (i.e., the
     * view scrolled its own contents). This is typically as a result of
     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
     * called.
     *
     * @param l Current horizontal scroll origin.
     * @param t Current vertical scroll origin.
     * @param oldl Previous horizontal scroll origin.
     * @param oldt Previous vertical scroll origin.
     */
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        notifySubtreeAccessibilityStateChangedIfNeeded();

        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
        }

        mBackgroundSizeChanged = true;
        mDefaultFocusHighlightSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        final AttachInfo ai = mAttachInfo;
        if (ai != null) {
            ai.mViewScrollChanged = true;
        }

        if (mListenerInfo != null && mListenerInfo.mOnScrollChangeListener != null) {
            mListenerInfo.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
        }
    }
 }
复制代码

View.AttachInfo是View的内部类,其注释已经描述了,当view滑动的时候把这个值设置成true。onScrollChanged也是View的protected的方法,而当ScrollView和NestScrollView的滑动状态被改变的时候就会调用这个方法,而这个方法内则就会把状态设置成true。

测试结果

经过在下的测试吧,OnScrollChangedListener在ScrollView和NestScrollView滑动的时候都会触发回调哦。而上述代码分析,则可以说明当两个滑动组件滑动的时候就会触发对应的回调监听。

View 出现一半

这个监控方法还是和上篇文章一样,请各位大佬直接看上篇文章就好了。

1.5s的曝光时长

先回到之前的文章提到onAttachedToWindow onDetachedFromWindow的两个方法,这两个可以用吗?答案肯定是不行的。那么我们应该怎么办呢??

没有枪没有炮,还是自己造吧。

interface ExposeViewAdapter {
    fun setExposeListener(listener: (Float) -> Unit)

    fun setExposeListener(listener: OnExposeListener)

    fun onVisibleChange(isCover: Boolean)
}
复制代码

首先我们可以先提供一个适配器,提供onVisibleChange这个方法来代替onAttachedToWindow onDetachedFromWindow

class ExposeScrollChangeListener(scrollView: ViewGroup) :
    ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener {

    private val rootView: ViewGroup? = scrollView.getChildAt(0) as ViewGroup?
    private val views = hashSetOf()
    private var lastChildCount = 0

    init {

    }

    override fun onScrollChanged() {
        views.forEach {
            val exposeView = it as ExposeViewAdapter
            exposeView.onVisibleChange(it.visibleRect())
        }
    }

    private fun checkViewSize() {
        rootView?.apply {
            lastChildCount = childCount
            getChildExpose(rootView)
        }
    }

    private fun getChildExpose(view: View?) {
        view?.let {
            if (it is ExposeViewAdapter) {
                views.add(it)
            }
            if (view is ViewGroup) {
                //遍历ViewGroup,是子view加1,是ViewGroup递归调用
                for (i in 0 until view.childCount) {
                    val child = view.getChildAt(i)
                    if (child is ExposeViewAdapter) {
                        views.add(child)
                    }
                    if (child is ViewGroup) {
                        getChildExpose(child)
                    }
                }
            }
        }
    }

    override fun onGlobalLayout() {
        val timeUsage = System.currentTimeMillis()
        checkViewSize()
        Log.i("expose", "timeCoast:${System.currentTimeMillis() - timeUsage}")
    }

}
复制代码

首先我们需要监控onGlobalLayout这个方法,在这个方法触发的情况下,去扫描当前的ViewTree,去获取实现了ExposeViewAdapter的所有的View。当滑动监听触发的时候调用之前的view是否被遮挡的方法来判断当前的view是不是在视图上出现了,然后调用onVisibleChange来通知视图是否已经从window上移除。

最后

面试官:哎哟不错哟。

我:谦虚有理的小菜逼。

面试官:这种方式感觉还是不够智能,如果让你用动态插桩呢。

我:打扰了,二营长,把我的意大利炮抬过来。

面试官:回家继续等通知把。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • View的有效曝光监控(上)|RecyclerView 篇

    我:之前我是把我们广告的曝光监控放在广告的模型层,然后在bindview的时候做一次曝光的,然后内部做了一次曝光防抖动,避免多次曝光。

    逮虾户
  • Lambda引发的惨案 | Desugar顺序变更

    这篇文章是紧接着上一篇文章的,原因就是因为有人在评论区留下了Lambad如何处理。根据我以往的经验,卧槽这个不是送分题吗,根据以往的经验,Lambda都会被脱糖...

    逮虾户
  • 最近面试碰到的两道算法题|面试相关

    最近阿里的一道面试题,其实基于多层博弈论,我想我刷过这题,我知道如何偷鸡的。我以为我在第二层,没想到我只在第一层。

    逮虾户
  • 自定义View学习——仿QQ消息气泡拖拽黏连删除

    该篇主要是对MessageBubbleView仿QQ消息控件的修改。因为我发现这个QQ消息气泡开源控件是规则的圆,所以稍加修改,对onDraw()绘画图形做了变...

    紫兮木溪
  • 1040 有几个PAT (25 分)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    韩旭051
  • 扫描代码是否有sql句柄没有关

    用户7962184
  • pta 习题集5-17 家谱处理

    人类学研究对于家族很感兴趣,于是研究人员搜集了一些家族的家谱进行研究。实验中,使用计算机处理家谱。为了实现这个目的,研究人员将家谱转换为文本文件。下面为家谱文...

    ShenduCC
  • 【Go 语言社区】算法课程 第一季 第4节 100以内的素数

    package main import ( "fmt" "math" ) func main() { var i, j, n int ...

    李海彬
  • 【GPLT】L1-060 心理阴影面积

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    喜欢ctrl的cxk
  • ios 版本更新提示-硬更新/软更新

    步骤一: 将检测更新写到APPDelegate的applicationDidBecomeActive中

    用户1219438

扫码关注云+社区

领取腾讯云代金券