在线直播系统源码,横向无限循环滚动的单行弹幕效果

在线直播系统源码,横向无限循环滚动的单行弹幕效果实现的相关代码

实现思路分析

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

 

1、弹幕布局是从屏幕的右侧向左侧滚动,单个弹幕之间的间距是固定的(设计要求)

2、弹幕要支持无限滚动,出于性能要求,如果不在屏幕内的,应该移除,不能无限追加到内存里面。

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

 

1、对于滚动和超出屏幕后移除,可以使用动画来实现,动画从屏幕右边开始移动到屏幕左边,监听如果已经动画结束,则remove掉布局。

2、无限循环效果,可以使用两个链表实现,一个保存加入到屏幕的弹幕数据(A),另一个保存未添加到屏幕的弹幕数据(B)。让进入屏幕前将布局从B中poll出来,添加到A中。反之,屏幕移除的时候从A中poll出来,添加到B中。

代码实现

首先创建出来一个弹幕数据对象类

 

 

1
data class Danmu(<br>    //头像<br>    var headerUrl: String? = null,<br>    //昵称<br>    var userName: String? = null,<br>    //信息<br>    var info: String? = null,<br>)

 

要被使用的弹幕itemView

 

 

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

 

 

接下来就是弹幕布局的容器类,用来控制动画和数据交替。注意代码中有很有用的注释

 

 

1
class DanmuView @JvmOverloads constructor(<br>    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0<br>) : FrameLayout(context, attrs, defStyleAttr) {<br>    private var mWidth = 0<br>    //为展示在屏幕上的弹幕数据<br>    private val mDanMuList = LinkedList<Danmu>()<br>    //屏幕中展示的弹幕数据<br>    private val mVisibleDanMuList = LinkedList<Danmu>()<br>    //判断是否在运行<br>    private val mIsRunning = AtomicBoolean(false)<br>    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {<br>        super.onMeasure(widthMeasureSpec, heightMeasureSpec)<br>        mWidth = measuredWidth<br>    }<br>    /**<br>     * 添加弹幕数据<br>     */<br>    fun enqueueDanMuList(danMuList: ArrayList<Danmu>) {<br>        danMuList.forEach {<br>            if (this.mDanMuList.contains(it).not()) {<br>                this.mDanMuList.add(it)<br>            }<br>        }<br>        if (mWidth == 0) {<br>            viewTreeObserver.addOnGlobalLayoutListener(object :<br>                ViewTreeObserver.OnGlobalLayoutListener {<br>                override fun onGlobalLayout() {<br>                    mWidth = measuredWidth<br>                    viewTreeObserver.removeOnGlobalLayoutListener(this)<br>                    if (mIsRunning.get().not()) {<br>                        mDanMuList.poll()?.apply {<br>                        //这里是用来处理布局的交替工作,前面分析有说明<br>                            mVisibleDanMuList.add(this)<br>                            createDanMuItemView(this)<br>                        }<br>                    }<br>                }<br>            })<br>        } else {<br>            if (mIsRunning.get().not()) {<br>                mDanMuList.poll()?.apply {<br>                //这里是用来处理布局的交替工作,前面分析有说明<br>                    mVisibleDanMuList.add(this)<br>                    createDanMuItemView(this)<br>                }<br>            }<br>        }<br>    }<br>    private fun startDanMuAnimate(danMuItemView: DanmuItemView) {<br>        var isInit = false<br>        danMuItemView.animate()<br>        //注意这边设置的便宜量是容器布局的宽度+弹幕item布局的宽度,这样就确保滚动值刚好是从屏幕右侧外到屏幕左侧外<br>            .translationXBy((-(mWidth + danMuItemView.measuredWidth)).toFloat())<br>            .setDuration(6000)<br>            .setInterpolator(LinearInterpolator())<br>            .setUpdateListener {<br>                val danMuTranslateX =<br>                    (mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float)<br>                    //这里是关键,用来确保每个item布局的间距一致,判断如果滚动进入屏幕的距离刚好是自身+20dp,也就是刚好空出来了20dp之后,紧接着下一个弹幕布局开始添加并动起来。<br>                if (danMuTranslateX >= danMuItemView.measuredWidth + Utils.convertDpToPixel(20F) && isInit.not()) {<br>                    isInit = true<br>                    mDanMuList.poll()?.apply {<br>                        mVisibleDanMuList.add(this)<br>                        createDanMuItemView(this)<br>                    }<br>                }<br>            }<br>            .setListener(object : AnimatorListenerAdapter() {<br>                override fun onAnimationEnd(animation: Animator?) {<br>                    if (mIsRunning.get().not()) {<br>                        mIsRunning.set(true)<br>                    }<br>                    //很重要,在动画结束,也就是布局从屏幕移除之后,切记从布局中移除掉,<br>                    //并且进行一波数据交替,方便实现无线循环<br>                    danMuItemView.danmu?.let {<br>                        mVisibleDanMuList.remove(it)<br>                        mDanMuList.add(it)<br>                    }<br>                    removeView(danMuItemView)<br>                }<br>            }).start()<br>    }<br>    private fun createDanMuItemView(danMu: Danmu) {<br>        val danMuItemView = DanmuItemView(context).apply {<br>            setDanmuEntity(danMu)<br>        }<br>        //这里将布局添加之后,默认便宜到屏幕右侧出屏幕,造成布局总是从右👉移动到👈左的效果。<br>        val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight)<br>        param.gravity = Gravity.CENTER_VERTICAL<br>        param.leftMargin = mWidth<br>        startDanMuAnimate(danMuItemView)<br>        addView(danMuItemView, param)<br>    }<br>}

 

以上就是在线直播系统源码,横向无限循环滚动的单行弹幕效果实现的相关代码, 更多内容欢迎关注之后的文章

 

posted @   云豹科技-苏凌霄  阅读(303)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示