Android实现横向无限循环滚动的单行弹幕效果
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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(yout.danmu_item, this, true)
danmuItemView = findViewById(DanmuItem)
}
fun setDanmuEntity(danmu: Danmu) {
this.danmu = danmu
danmuItemView?.text = "我是⼀个弹幕~~~~~哈哈哈哈哈哈" + erName
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)
}
}
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。