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