欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實(shí)現(xiàn)橫向無限循環(huán)滾動(dòng)的單行彈幕效果

 更新時(shí)間:2021年06月23日 15:06:25   作者:飄渺包子  
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)橫向無限循環(huán)滾動(dòng)的單行彈幕效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

本期將帶領(lǐng)大家實(shí)現(xiàn)一個(gè)這樣的效果,支持無限循環(huán)的單行彈幕效果。

實(shí)現(xiàn)思路分析

要實(shí)現(xiàn)上面的效果,我們先拆分下實(shí)現(xiàn)要素:

1、彈幕布局是從屏幕的右側(cè)向左側(cè)滾動(dòng),單個(gè)彈幕之間的間距是固定的(設(shè)計(jì)要求)
2、彈幕要支持無限滾動(dòng),出于性能要求,如果不在屏幕內(nèi)的,應(yīng)該移除,不能無限追加到內(nèi)存里面。

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

1、對于滾動(dòng)和超出屏幕后移除,可以使用動(dòng)畫來實(shí)現(xiàn),動(dòng)畫從屏幕右邊開始移動(dòng)到屏幕左邊,監(jiān)聽如果已經(jīng)動(dòng)畫結(jié)束,則remove掉布局。

2、無限循環(huán)效果,可以使用兩個(gè)鏈表實(shí)現(xiàn),一個(gè)保存加入到屏幕的彈幕數(shù)據(jù)(A),另一個(gè)保存未添加到屏幕的彈幕數(shù)據(jù)(B)。讓進(jìn)入屏幕前將布局從B中poll出來,添加到A中。反之,屏幕移除的時(shí)候從A中poll出來,添加到B中。

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

首先創(chuàng)建出來一個(gè)彈幕數(shù)據(jù)對象類

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 = "我是一個(gè)彈幕~~~~~哈哈哈哈哈哈" + danmu.userName
        measure(0, 0)
    }
}

接下來就是彈幕布局的容器類,用來控制動(dòng)畫和數(shù)據(jù)交替。注意代碼中有很有用的注釋

class DanmuView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private var mWidth = 0

    //為展示在屏幕上的彈幕數(shù)據(jù)
    private val mDanMuList = LinkedList<Danmu>()

    //屏幕中展示的彈幕數(shù)據(jù)
    private val mVisibleDanMuList = LinkedList<Danmu>()

    //判斷是否在運(yùn)行
    private val mIsRunning = AtomicBoolean(false)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = measuredWidth
    }

    /**
     * 添加彈幕數(shù)據(jù)
     */
    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()
        //注意這邊設(shè)置的便宜量是容器布局的寬度+彈幕item布局的寬度,這樣就確保滾動(dòng)值剛好是從屏幕右側(cè)外到屏幕左側(cè)外
            .translationXBy((-(mWidth + danMuItemView.measuredWidth)).toFloat())
            .setDuration(6000)
            .setInterpolator(LinearInterpolator())
            .setUpdateListener {

                val danMuTranslateX =
                    (mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float)
                    //這里是關(guān)鍵,用來確保每個(gè)item布局的間距一致,判斷如果滾動(dòng)進(jìn)入屏幕的距離剛好是自身+20dp,也就是剛好空出來了20dp之后,緊接著下一個(gè)彈幕布局開始添加并動(dòng)起來。
                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)
                    }
                    //很重要,在動(dòng)畫結(jié)束,也就是布局從屏幕移除之后,切記從布局中移除掉,
                    //并且進(jìn)行一波數(shù)據(jù)交替,方便實(shí)現(xiàn)無線循環(huán)
                    danMuItemView.danmu?.let {
                        mVisibleDanMuList.remove(it)
                        mDanMuList.add(it)
                    }
                    removeView(danMuItemView)
                }
            }).start()
    }

    private fun createDanMuItemView(danMu: Danmu) {
        val danMuItemView = DanmuItemView(context).apply {
            setDanmuEntity(danMu)
        }
        //這里將布局添加之后,默認(rèn)便宜到屏幕右側(cè)出屏幕,造成布局總是從右👉移動(dòng)到👈左的效果。
        val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight)
        param.gravity = Gravity.CENTER_VERTICAL
        param.leftMargin = mWidth
        startDanMuAnimate(danMuItemView)
        addView(danMuItemView, param)
    }
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論