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

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

实现思路分析

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

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)

}

}

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

以上是 Android实现横向无限循环滚动的单行弹幕效果 的全部内容, 来源链接: utcz.com/p/243701.html

回到顶部