Android實戰——RecyclerView條目曝光埋點

一、概要

100行代碼實現recyclerview條目曝光埋點設計

二、設計思路

  1. 條目露出來一半以上視為該條目曝光。
  2. 在rv滾動過程中或者數據變更回調OnGlobalLayoutListener時,將符合條件1的條目記錄在曝光列表、上傳埋點集合里。
  3. 滾動狀態變更和OnGlobalLayoutListener回調時,且列表狀態為idle狀態,觸發上報埋點。

三、容錯性

  1. 滑動過快時,視為未曝光
  2. 數據變更時,重新檢測曝光
  3. 曝光過的條目,不會重復曝光

四、接入影響

  1. 對業務代碼零侵入
  2. 對列表滑動體驗無影響

五、代碼實現

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import java.util.*

class RVItemExposureListener(
    private val mRecyclerView: RecyclerView,
    private val mExposureListener: IOnExposureListener?
) {
    interface IOnExposureListener {
        fun onExposure(position: Int)
        fun onUpload(exposureList: List<Int>?): Boolean
    }

    private val mExposureList: MutableList<Int> = ArrayList()
    private val mUploadList: MutableList<Int> = ArrayList()
    private var mScrollState = 0

    var isEnableExposure = true
    private var mCheckChildViewExposure = true

    private val mViewVisible = Rect()
    fun checkChildExposeStatus() {
        if (!isEnableExposure) {
            return
        }
        val length = mRecyclerView.childCount
        if (length != 0) {
            var view: View?
            for (i in 0 until length) {
                view = mRecyclerView.getChildAt(i)
                if (view != null) {
                    view.getLocalVisibleRect(mViewVisible)
                    if (mViewVisible.height() > view.height / 2 && mViewVisible.top < mRecyclerView.bottom) {
                        checkExposure(view)
                    }
                }
            }
        }
    }

    private fun checkExposure(childView: View): Boolean {
        val position = mRecyclerView.getChildAdapterPosition(childView)
        if (position < 0 || mExposureList.contains(position)) {
            return false
        }
        mExposureList.add(position)
        mUploadList.add(position)
        mExposureListener?.onExposure(position)
        return true
    }

    private fun uploadList() {
        if (mScrollState == RecyclerView.SCROLL_STATE_IDLE && mUploadList.size > 0 && mExposureListener != null) {
            val success = mExposureListener.onUpload(mUploadList)
            if (success) {
                mUploadList.clear()
            }
        }
    }

    init {
        mRecyclerView.viewTreeObserver.addOnGlobalLayoutListener {
            if (mRecyclerView.childCount == 0 || !mCheckChildViewExposure) {
                return@addOnGlobalLayoutListener
            }
            checkChildExposeStatus()
            uploadList()
            mCheckChildViewExposure = false
        }
        mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(
                recyclerView: RecyclerView,
                newState: Int
            ) {
                super.onScrollStateChanged(recyclerView, newState)
                mScrollState = newState
                uploadList()
            }

            override fun onScrolled(
                recyclerView: RecyclerView,
                dx: Int,
                dy: Int
            ) {
                super.onScrolled(recyclerView, dx, dy)
                if (!isEnableExposure) {
                    return
                }

                // 大于50視為滑動過快
                if (mScrollState == RecyclerView.SCROLL_STATE_SETTLING && Math.abs(dy) > 50) {
                    return
                }
                checkChildExposeStatus()
            }
        })
    }
}

六、使用

RVItemExposureListener(yourRecyclerView, object : RVItemExposureListener.IOnExposureListener {
    override fun onExposure(position: Int) {
        // 滑動過程中出現的條目
        Log.d("exposure-curPosition:", position.toString())
    }

    override fun onUpload(exposureList: List<Int>?): Boolean {
        Log.d("exposure-positionList", exposureList.toString())
        // 上報成功后返回true
        return true
    }

})

完結,撒花??

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容