文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android实现横向无限循环滚动的单行弹幕效果

2024-04-02 19:55

关注

本期将带领大家实现一个这样的效果,支持无限循环的单行弹幕效果。

实现思路分析

要实现上面的效果,我们先拆分下实现要素:

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)
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯