美文网首页
Android中条目曝光统计

Android中条目曝光统计

作者: 馒头炖土豆 | 来源:发表于2023-02-08 14:34 被阅读0次
    最近项目中需要对RecyclerView列表进行曝光统计,在经过数个方案的对比之后,我选择了下面这个方案,这是迄今为止我认为最好的方案借鉴链接如下:
    https://juejin.cn/post/7028829716492582948

    下面是整理的该方案的代码,具体实现逻辑讲解自行查看原文

    import android.content.Context
    import android.util.AttributeSet
    import android.widget.FrameLayout
    
    //ExposureLayout
    //用于曝光埋点统计,放在布局的最外层
    //参考链接:https://juejin.cn/post/7028829716492582948
    class ExposureLayout : FrameLayout {
        private val mExposureHandler by lazy { ExposureHandler(this) }
    
        constructor(context: Context) : super(context)
        constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
        constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    
        //添加到视图
        override fun onAttachedToWindow() {
            super.onAttachedToWindow()
            mExposureHandler.onAttachedToWindow()
        }
    
        //从视图中移除
        override fun onDetachedFromWindow() {
            super.onDetachedFromWindow()
            mExposureHandler.onDetachedFromWindow()
        }
    
        //视图焦点改变
        override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
            super.onWindowFocusChanged(hasWindowFocus)
            mExposureHandler.onWindowFocusChanged(hasWindowFocus)
        }
    
        //视图可见性
        override fun onVisibilityAggregated(isVisible: Boolean) {
            super.onVisibilityAggregated(isVisible)
            mExposureHandler.onVisibilityAggregated(isVisible)
        }
    
        //曝光回调
        fun setExposureCallback(callback: IExposureCallback) {
            mExposureHandler.setExposureCallback( callback)
        }
    
        // 设置曝光条件 曝光区域大小,例如展示超过50%才算曝光
        fun setShowRatio(ratio: Float) {
            mExposureHandler.setShowArea(ratio)
        }
    
        // 设置曝光最小时间,例如必须曝光超过两秒才算曝光
        fun setTimeLimit(timeLimit:Int) {
            mExposureHandler.setTimeLimit(timeLimit)
        }
    }
    
    import android.graphics.Rect
    import android.view.View
    import android.view.ViewTreeObserver
    
    //用于曝光埋点统计
    //参考链接:https://juejin.cn/post/7028829716492582948
    class ExposureHandler (private val view: View) : ViewTreeObserver.OnPreDrawListener {
        private var mAttachedToWindow = false //添加到视图中的状态
        private var mHasWindowFocus = true // 视图获取到焦点的状态,默认为true,避免某些场景不被调用
        private var mVisibilityAggregated = true //可见性的状态,默认为true,避免某些场景不被调用
        private var mExposure = false //当前是否处于曝光状态
        private var mExposureCallback: IExposureCallback? = null //曝光回调
        private var mStartExposureTime: Long = 0L//开始曝光时间戳
        private var mShowRatio: Float = 0f//曝光条件超过多大面积0~1f
        private var mTimeLimit: Int = 0 //曝光条件超过多久才算曝光,例如2秒(2000)
        private val mRect = Rect() //实时曝光面积
    
        //添加到视图时添加OnPreDrawListener
        fun onAttachedToWindow() {
            mAttachedToWindow = true
            view.viewTreeObserver.addOnPreDrawListener(this)
        }
    
        /**
         * 从视图中移除时去掉OnPreDrawListener
         * 尝试取消曝光
         */
        fun onDetachedFromWindow() {
            mAttachedToWindow = false
            view.viewTreeObserver.removeOnPreDrawListener(this)
            tryStopExposure()
        }
    
        /**
         * 视图焦点改变
         * 尝试取消曝光
         */
        fun onWindowFocusChanged(hasWindowFocus: Boolean) {
            mHasWindowFocus = hasWindowFocus
            if(!hasWindowFocus){
                tryStopExposure()
            }
        }
    
        /**
         * 可见性改变
         * 尝试取消曝光
         */
        fun onVisibilityAggregated(isVisible: Boolean) {
            mVisibilityAggregated = isVisible
            if(!isVisible){
                tryStopExposure()
            }
        }
    
        /**
         * 视图预绘制
         * 当曝光面积达到条件是尝试曝光
         * 当视图面积不满足条件时尝试取消曝光
         */
        override fun onPreDraw(): Boolean{
            val visible = view.getLocalVisibleRect(mRect)&&view.isShown //获取曝光面积和View的Visible
            if(!visible){
                tryStopExposure()//不达到曝光条件时尝试取消曝光
                return true
            }
            if(mShowRatio>0){//存在曝光面积限制条件时
                if(kotlin.math.abs(mRect.bottom-mRect.top)>view.height*mShowRatio &&kotlin.math.abs(mRect.right-mRect.left)>view.width*mShowRatio)
                {
                    tryExposure()//达到曝光条件时尝试曝光
                }else{
                    tryStopExposure()//不达到曝光条件时尝试取消曝光
                }
             }else{
                tryExposure()//达到曝光条件时尝试曝光
             }
            return true
        }
        /**
         * 曝光回调
         */
        fun setExposureCallback(callback: IExposureCallback) {
            mExposureCallback = callback
        }
    
        /**
         * 设置曝光面积条件
         */
        fun setShowArea(area: Float) {
            mShowRatio = area
        }
    
        /**
         * 设置曝光时间限制条件
         */
        fun setTimeLimit(index: Int) {
            this.mTimeLimit = index
        }
    
        /**
         * 尝试曝光
         */
        private fun tryExposure(){
            if(mAttachedToWindow && mHasWindowFocus && mVisibilityAggregated && !mExposure){
                mExposure = true//曝光中
                mStartExposureTime = System.currentTimeMillis()//曝光开始时间
                if(mTimeLimit == 0)
                {
                    mExposureCallback?.show()//回调开始曝光
                }
            }
        }
    
        /**
         *尝试取消曝光
        */
        private fun tryStopExposure() {
            if(mExposure){
                mExposure = false//重置曝光状态
                if(mTimeLimit >0 && System.currentTimeMillis()- mStartExposureTime > mTimeLimit){
                    //满足时长限制曝光
                    mExposureCallback?.show()
                }
            }
        }
    }
    
    //用于曝光埋点统计
    //参考链接:https://juejin.cn/post/7028829716492582948
    interface IExposureCallback {
        fun show() //曝光
    }
    

    相关文章

      网友评论

          本文标题:Android中条目曝光统计

          本文链接:https://www.haomeiwen.com/subject/mhuqkdtx.html