Android實(shí)戰(zhàn)——RecyclerView條目曝光埋點(diǎn)

一、概要

100行代碼實(shí)現(xiàn)recyclerview條目曝光埋點(diǎn)設(shè)計(jì)

二、設(shè)計(jì)思路

  1. 條目露出來一半以上視為該條目曝光。
  2. 在rv滾動(dòng)過程中或者數(shù)據(jù)變更回調(diào)OnGlobalLayoutListener時(shí),將符合條件1的條目記錄在曝光列表、上傳埋點(diǎn)集合里。
  3. 滾動(dòng)狀態(tài)變更和OnGlobalLayoutListener回調(diào)時(shí),且列表狀態(tài)為idle狀態(tài),觸發(fā)上報(bào)埋點(diǎn)。

三、容錯(cuò)性

  1. 滑動(dòng)過快時(shí),視為未曝光
  2. 數(shù)據(jù)變更時(shí),重新檢測曝光
  3. 曝光過的條目,不會(huì)重復(fù)曝光

四、接入影響

  1. 對業(yè)務(wù)代碼零侵入
  2. 對列表滑動(dòng)體驗(yàn)無影響

五、代碼實(shí)現(xiàn)

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視為滑動(dòng)過快
                if (mScrollState == RecyclerView.SCROLL_STATE_SETTLING && Math.abs(dy) > 50) {
                    return
                }
                checkChildExposeStatus()
            }
        })
    }
}

六、使用

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

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

})

完結(jié),撒花??

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

推薦閱讀更多精彩內(nèi)容