This installment will take you through the implementation of one such effect, support infinite loop single line barrage effect.
Analysis of implementation ideas
To achieve the above effect, we first break down the implementation elements:
- 1. The layout of the barrage scrolls from the right side of the screen to the left side, and the spacing between individual barrage is fixed (design requirements)
- 2. Danmu should support unlimited scrolling. For performance requirements, if it is not in the screen, it should be removed and cannot be added to the memory indefinitely.
After splitting the demand elements, for the above demand elements, do a solution:
- 1, For scrolling and off-screen removal, you can use animation to achieve, animation from the right side of the screen to the left side of the screen, listen if the animation has finished, then remove the layout.
- 2. Infinite loop effect can be realized by using two linked lists, one to save the barrage data added to the screen (A), and the other to save the barrage data not added to the screen (B). Poll the layout from B and add it to A before entering the screen. Conversely, when the screen is removed, it polles out of A and adds to B.
Code implementation
First create a barrage data object class
data class Danmu(
/ / avatar
var headerUrl: String? = null./ / nickname
var userName: String? = null./ / information
var info: String? = null.)Copy the code
The barrage itemView to be used
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 ="I am a barrage ~~~~~ hahahahahaha" + danmu.userName
measure(0.0)}}Copy the code
Next up is the container class for the barrage layout, which controls animation and data interchange. Note the useful comments in the code
class DanmuView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private var mWidth = 0
// To display the barrage data on the screen
private val mDanMuList = LinkedList<Danmu>()
// The barrage data displayed on the screen
private val mVisibleDanMuList = LinkedList<Danmu>()
// Check if it is running
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(a) {
mWidth = measuredWidth
viewTreeObserver.removeOnGlobalLayoutListener(this)
if (mIsRunning.get().not()) { mDanMuList.poll()? .apply {// This is used to handle alternate layout work, as explained in the previous analysis
mVisibleDanMuList.add(this)
createDanMuItemView(this)}}}})}else {
if (mIsRunning.get().not()) { mDanMuList.poll()? .apply {// This is used to handle alternate layout work, as explained in the previous analysis
mVisibleDanMuList.add(this)
createDanMuItemView(this)}}}}private fun startDanMuAnimate(danMuItemView: DanmuItemView) {
var isInit = false
danMuItemView.animate()
// Note that the value set here is the width of the container layout + the width of the marquee item layout, so that the scroll value is exactly from the right to the left of the screen
.translationXBy((-(mWidth + danMuItemView.measuredWidth)).toFloat())
.setDuration(6000)
.setInterpolator(LinearInterpolator())
.setUpdateListener {
val danMuTranslateX =
(mWidth + danMuItemView.measuredWidth) * (it.animatedValue as Float)
// This is the key, used to ensure that the spacing of each item layout is consistent. Judge if the scrolling distance into the screen is just +20dp of itself, that is, 20DP is just empty, then the next barrage layout starts to add and move.
if (danMuTranslateX >= danMuItemView.measuredWidth + Utils.convertDpToPixel(20F) && isInit.not()) {
isInit = truemDanMuList.poll()? .apply { mVisibleDanMuList.add(this)
createDanMuItemView(this)
}
}
}
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?). {
if (mIsRunning.get().not()) {
mIsRunning.set(true)}// It is important to remember to remove from the layout at the end of the animation, after the layout is removed from the screen,
// And a wave of data interchange, convenient to achieve wireless loopdanMuItemView.danmu? .let { mVisibleDanMuList.remove(it) mDanMuList.add(it) } removeView(danMuItemView) } }).start() }private fun createDanMuItemView(danMu: Danmu) {
val danMuItemView = DanmuItemView(context).apply {
setDanmuEntity(danMu)
}
// After the layout is added here, it will default to the right side of the screen, resulting in the layout always moving from right 👉 to 👈 left.
val param = LayoutParams(danMuItemView.measuredWidth, danMuItemView.measuredHeight)
param.gravity = Gravity.CENTER_VERTICAL
param.leftMargin = mWidth
startDanMuAnimate(danMuItemView)
addView(danMuItemView, param)
}
}
Copy the code