線上直播系統原始碼,橫向無限迴圈滾動的單行彈幕效果

zhibo系統開發發表於2021-11-01

線上直播系統原始碼,橫向無限迴圈滾動的單行彈幕效果實現的相關程式碼

實現思路分析

要實現上面的效果,我們先拆分下實現要素:


1、彈幕布局是從螢幕的右側向左側滾動,單個彈幕之間的間距是固定的(設計要求)

2、彈幕要支援無限滾動,出於效能要求,如果不在螢幕內的,應該移除,不能無限追加到記憶體裡面。

拆分完需求要素之後,針對上面的需求要素,做一下思路解答:


1、對於滾動和超出螢幕後移除,可以使用動畫來實現,動畫從螢幕右邊開始移動到螢幕左邊,監聽如果已經動畫結束,則remove掉佈局。

2、無限迴圈效果,可以使用兩個連結串列實現,一個儲存加入到螢幕的彈幕資料(A),另一個儲存未新增到螢幕的彈幕資料(B)。讓進入螢幕前將佈局從B中poll出來,新增到A中。反之,螢幕移除的時候從A中poll出來,新增到B中。

程式碼實現

首先建立出來一個彈幕資料物件類


data class Danmu(
    //頭像
    var headerUrl: String? = null,
    //暱稱
    var userName: String? = null,
    //資訊
    var info: String? = null,
)



要被使用的彈幕itemView


class DanmuItemView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayoutCompat(context, attrs, defStyleAttr) {
    private var danmuItemView: TextView? = null
    var danmu: Danmu? = null
    init {
        LayoutInflater.from(context).inflate(R.layout.danmu_item, this, true)
        danmuItemView = findViewById(R.id.tvDanmuItem)
    }
    fun setDanmuEntity(danmu: Danmu) {
        this.danmu = danmu
        danmuItemView?.text = "我是一個彈幕~~~~~哈哈哈哈哈哈" + danmu.userName
        measure(0, 0)
    }
}



接下來就是彈幕布局的容器類,用來控制動畫和資料交替。注意程式碼中有很有用的註釋


class DanmuView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
    private var mWidth = 0
    //為展示在螢幕上的彈幕資料
    private val mDanMuList = LinkedList<Danmu>()
    //螢幕中展示的彈幕資料
    private val mVisibleDanMuList = LinkedList<Danmu>()
    //判斷是否在執行
    private val mIsRunning = AtomicBoolean(false)
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = measuredWidth
    }
    /**
     * 新增彈幕資料
     */
    fun enqueueDanMuList(danMuList: ArrayList<Danmu>) {
        danMuList.forEach {
            if (this.mDanMuList.contains(it).not()) {
                this.mDanMuList.add(it)
            }
        }
        if (mWidth == 0) {
            viewTreeObserver.addOnGlobalLayoutListener(object :
                ViewTreeObserver.OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    mWidth = measuredWidth
                    viewTreeObserver.removeOnGlobalLayoutListener(this)
                    if (mIsRunning.get().not()) {
                        mDanMuList.poll()?.apply {
                        //這裡是用來處理佈局的交替工作,前面分析有說明
                            mVisibleDanMuList.add(this)
                            createDanMuItemView(this)
                        }
                    }
                }
            })
        } else {
            if (mIsRunning.get().not()) {
                mDanMuList.poll()?.apply {
                //這裡是用來處理佈局的交替工作,前面分析有說明
                    mVisibleDanMuList.add(this)
                    createDanMuItemView(this)
                }
            }
        }
    }
    private fun startDanMuAnimate(danMuItemView: DanmuItemView) {
        var isInit = false
        danMuItemView.animate()
        //注意這邊設定的便宜量是容器佈局的寬度+彈幕item佈局的寬度,這樣就確保滾動值剛好是從螢幕右側外到螢幕左側外
            .translationXBy((-(mWidth + danMuItemView.measuredWidth)).toFloat())
            .setDuration(6000)
            .setInterpolator(LinearInterpolator())
            .setUpdateListener {
                val danMuTranslateX =
                    (mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float)
                    //這裡是關鍵,用來確保每個item佈局的間距一致,判斷如果滾動進入螢幕的距離剛好是自身+20dp,也就是剛好空出來了20dp之後,緊接著下一個彈幕布局開始新增並動起來。
                if (danMuTranslateX >= danMuItemView.measuredWidth + Utils.convertDpToPixel(20F) && isInit.not()) {
                    isInit = true
                    mDanMuList.poll()?.apply {
                        mVisibleDanMuList.add(this)
                        createDanMuItemView(this)
                    }
                }
            }
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    if (mIsRunning.get().not()) {
                        mIsRunning.set(true)
                    }
                    //很重要,在動畫結束,也就是佈局從螢幕移除之後,切記從佈局中移除掉,
                    //並且進行一波資料交替,方便實現無線迴圈
                    danMuItemView.danmu?.let {
                        mVisibleDanMuList.remove(it)
                        mDanMuList.add(it)
                    }
                    removeView(danMuItemView)
                }
            }).start()
    }
    private fun createDanMuItemView(danMu: Danmu) {
        val danMuItemView = DanmuItemView(context).apply {
            setDanmuEntity(danMu)
        }
        //這裡將佈局新增之後,預設便宜到螢幕右側出螢幕,造成佈局總是從右?移動到?左的效果。
        val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight)
        param.gravity = Gravity.CENTER_VERTICAL
        param.leftMargin = mWidth
        startDanMuAnimate(danMuItemView)
        addView(danMuItemView, param)
    }
}


以上就是線上直播系統原始碼,橫向無限迴圈滾動的單行彈幕效果實現的相關程式碼, 更多內容歡迎關注之後的文章


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69978258/viewspace-2839989/,如需轉載,請註明出處,否則將追究法律責任。

相關文章