Custom View actual combat "Barrage"

foreword

I have learned that the custom View "gradient text" inherits from View. Now we are inheriting ViewGroup to realize our "Danmaku" View.
This article is divided into three parts

1. Step-by-step explanation and step-by-step actual combat
2. Cache optimization
3. The three-party bullet screen framework DanmakuFlameMaster
4. The complete code of the first step

Is there any obvious difference between the View inherited from View and the View implemented by ViewGroup?
One sentence to get through your two veins:
Custom View mainly implements onMeasure and onDraw
Custom ViewGroup mainly implements onMeasure and onLayout

Well, now let's implement our "Bullet Screen".

1. Steps to explain

Realizing barrage sounds complicated, but it is actually very simple. If you don’t believe me, read on.
Steps:
1. Initialize the barrage sub-View

  • You can customize the barrage sub-View (I will replace it with TextView first)
  • Add method can be single and multiple
fun addBarrage(data: BarrageItem){
    
     //BarrageItem自定义的View的数据实体
    //1、初始化弹幕子View
    val childView : TextView = TextView(context).apply {
    
    
    text = data.text
    textSize = data.textSize
    setTextColor(ContextCompat.getColor(context, R.color.white))
}

fun addBarrageList(dataList : MutableList<BarrageItem>){
    
    
    this.barrageList = dataList
    dataList.forEach {
    
    
        addBarrage(it)
    }
}

2. Measure sub-View

  • The measurement must be onMeasure. In the previous "View Drawing Process", the two parameters widthMeasureSpec and heightMeasureSpec of the child onMeasure are the measurement information given to him by the parent View.
  • Here my barrage is the size of the entire View, and match_parent is used in XML, so there is no need to write its onMeasue method
//2、测量弹幕子View,也就是measure呗
childView.measure(measuredWidth, measuredHeight)

3. Add child View to ViewGroup

//3、添加弹幕子View
addView(childView)

4. After the measurement is completed and the addition is completed, it is time to place the position

  • Isn't the placement the same as onLayout?
//4、设置弹幕子View的布局,也就是layout呗
//这里我想让他从右面到左面移动,上下的位置是随机的
val left = measuredWidth
val top = nextInt(1, measuredHeight) //kotlin的Random.nextInt函数
childView.layout(left, top, left + childView.measuredWidth, top + childView.measuredHeight)

5. Turn on the animation of barrage scrolling

  • Isn't the animation from left to right very simple?
  • It is relatively simple to use TranslateAnimation
  • But later, you need to expand the effect or use ValueAnimator (transparency, rotation angle, scaling ratio)
//5、开启弹幕子View动画
val anim : ValueAnimator = ValueAnimator.ofFloat(1.0f).apply {
    
    
    duration = 7000
    interpolator = LinearInterpolator() //线性的插值器
    addUpdateListener {
    
    
        val value = it.animatedFraction
        val left = (measuredWidth - value *(measuredWidth + childView.measuredWidth)).toInt()
        //通过改变布局来实现弹幕的滚动
        childView.layout(left, top, left + childView.measuredWidth, top + childView.measuredHeight)
    }

    addListener(onEnd = {
    
    
        removeView(childView)
        barrageList.remove(data)
    })
}

anim.start()

The barrage of the first version is complete.
I'll put the complete code at the end, but it's too messy if it's in the middle.

2. Cache optimization

In fact, everyone can understand it at a glance, so if it is a little more advanced, it must be optimized.
Then the most obvious optimization must be the resources consumed by View's drawing. Because in this way, every time a barrage is sent, it means that a sub-view of the barrage needs to be drawn, which must consume resources.

Is there any way to reduce the pressure, and we were inspired by the cache of RecyclerView explained earlier.

1. We do not destroy the bullet screen sub-View that is removed from the screen, but cache it for him.
2. When we need to add a new bullet screen sub-View, reuse it for him, which will reduce the drawing. Reduce the pressure
3. In this way, the caching idea of ​​RecyclerView is used and optimized

Step 1: Use SimplePool

Android provides a buffer pool Pools class in androidx.core.util. To help developers cache some views, objects, etc.
We use his implementation class SimplePool to help us implement caching.
First look at the source code in SimplePool: (Relevant explanations are written in the comments)

public class SimplePool<T> implements Pools.Pool<T> {
    
    
    // 池对象容器
    private final Object[] mPool;
    // 池大小
    private int mPoolSize;

    public SimplePool(int maxPoolSize) {
    
    
        if (maxPoolSize <= 0) {
    
    
            throw new IllegalArgumentException("The max pool size must be > 0");
        }
        // 构造池对象容器
        mPool = new Object[maxPoolSize];
    }

    // 从池容器中获取对象
    public T acquire() {
    
    
        if (mPoolSize > 0) {
    
    
            // 总是从池容器末尾读取对象
            final int lastPooledIndex = mPoolSize - 1;
            T instance = (T) mPool[lastPooledIndex];
            mPool[lastPooledIndex] = null;
            mPoolSize--;
            return instance;
        }
        return null;
    }

    // 释放对象并存入池
    @Override
    public boolean release(@NonNull T instance) {
    
    
        if (isInPool(instance)) {
    
    
            throw new IllegalStateException("Already in the pool!");
        }
        // 总是将对象存到池尾
        if (mPoolSize < mPool.length) {
    
    
            mPool[mPoolSize] = instance;
            mPoolSize++;
            return true;
        }
        return false;
    }

    // 判断对象是否在池中
    private boolean isInPool(@NonNull T instance) {
    
    
        // 遍历池对象
        for (int i = 0; i < mPoolSize; i++) {
    
    
            if (mPool[i] == instance) {
    
    
                return true;
            }
        }
        return false;
    }
}

Step 2: Apply to the barrage View

1. Initialize the barrage pool

// 弹幕池
private var pool: Pools.SimplePool<TextView> = Pools.SimplePool(10)

2. Get the cached barrage space (here I use TextView, you can pass any View because its SimplePool is generic)

//1、初始化弹幕子View
//pool.acquire() 失败之后 就创建一个
val childView = pool.acquire() ?: TextView(context).apply {
    
    
    text = data.text
    textSize = data.textSize
}

3. Cache it when the view is destroyed at the end of the animation

addListener(onEnd = {
    
    
    removeView(childView)
    pool.release(childView)
    barrageList.remove(data)
})

In this way, our barrage reduces the pressure of re-creation, hee hee. Simple little optimization.

3. Danmaku framework DanmakuFlameMaster

Introducing Danmaku FlameMaster

1. Analysis of bullet chat data: DanmakuFlameMaster supports multiple bullet chat data formats, including ASS, XML, JSON, etc. It first parses the barrage data according to different formats, and converts it into a unified data structure.

2. Layout and rendering of bullet chatting: by laying out the parsed bullet chatting data, determine the position and display time of each bullet chatting on the screen. Then, using image rendering technology, the bullet chatting is drawn on the screen.

3. Danmaku control and interaction: DanmakuFlameMaster provides a wealth of bullet chat control and interaction functions. Users can set the display style, font color, speed and other properties of the bullet chatting. At the same time, it also supports operations such as pausing, playing, and blocking barrage.

4. Optimization and performance improvement of danmaku: In order to improve the rendering efficiency and user experience of danmaku, DanmakuFlameMaster adopts multi-threading technology and caching strategy. It separates the rendering and display of the bullet chat, so that the processing and rendering of the bullet chat can be performed in parallel, thereby improving the overall performance and efficiency.

In general, DanmakuFlameMaster realizes the efficient display and optimization of bullet chat through the analysis and rendering of bullet chat data, as well as the control and interaction of bullet chat. It is widely used in many barrage video websites and applications.

Use of Danmaku FlameMaster

1. Import the DanmakuFlameMaster library
First, add the following dependencies to your project's build.gradle file to import the DanmakuFlameMaster library:

implementation 'com.github.ctiao:DanmakuFlameMaster:0.8.7'

Then, do a sync operation to make sure the dependencies are imported successfully.
2. The basic use of DanmakuView
Add the DanmakuView control in your layout file and set related properties. For example, specify width, height, background color, etc.:

<su.litvak.danmaku.view.DanmakuView
    android:id="@+id/danmaku_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000" />

In the code, get an instance of DanmakuView, and perform basic configuration and initialization operations:

DanmakuView danmakuView = findViewById(R.id.danmaku_view);
danmakuView.enableDanmakuDrawingCache(true);
danmakuView.enableDanmakuDropping(true);
danmakuView.setCallback(new DrawHandler.Callback() {
    
    
    @Override
    public void prepared() {
    
    
        danmakuView.start();
    }

    @Override
    public void updateTimer(DanmakuTimer timer) {
    
    
        
    }

    @Override
    public void danmakuShown(BaseDanmaku danmaku) {
    
    
        
    }

    @Override
    public void drawingFinished() {
    
    
        
    }
});
danmakuView.prepare(parser, mContext);  // 通过解析器解析弹幕数据

3. Parse and add bullet chat data
DanmakuFlameMaster supports parsing and adding bullet chat data from various sources, including local files, URLs, InputStream, etc.
For example, to parse and add bullet chatting data from local files:

BaseDanmakuParser parser = new BiliDanmukuParser();
DefaultDanmakuContext danmakuContext = DefaultDanmakuContext.create();
danmakuView.prepare(parser, danmakuContext);
danmakuView.showFPS(true);  // 如果需要显示FPS,可以设置为true

try {
    
    
    FileInputStream fis = new FileInputStream("path/to/your/danmaku.xml");
    danmakuView.loadData(parser, fis);
} catch (FileNotFoundException e) {
    
    
    e.printStackTrace();
}

In addition, DanmakuFlameMaster also provides a variety of configuration parameters, such as font, font size, display area, etc., developers can customize settings according to needs.
4. Control the play and pause of bullet chat
DanmakuView provides some methods for controlling the playback and pause of bullet chat. For example:

danmakuView.start();  // 开始播放弹幕
danmakuView.pause();  // 暂停播放弹幕
danmakuView.resume();  // 恢复播放弹幕
danmakuView.stop();  // 停止播放弹幕
danmakuView.release();  // 释放资源

5. Sending and blocking bullet chatting
DanmakuFlameMaster also supports users to send bullet chatting and block bullet chatting of specified types. For example:
to send barrage:

BaseDanmaku danmaku = danmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
danmaku.text = "这是一条弹幕";
danmaku.padding = 5;
danmaku.textSize = 25f;
danmaku.textColor = Color.WHITE;
danmaku.setTime(danmakuView.getCurrentTime() + 1200);
danmakuView.addDanmaku(danmaku);

Block the bullet chatting of the specified type:

// 屏蔽顶部弹幕
danmakuContext.setDanmakuFilter(new IDanmakuFilter() {
    
    
    @Override
    public boolean filter(BaseDanmaku danmaku, int index, danmakuContext context) {
    
    
        if (danmaku.getType() == BaseDanmaku.TYPE_SCROLL_LR) {
    
    
            return false;
        }
        return true;
    }
});

Ok, the use of DanmakuFlameMaster is explained here, there are still many ways to explore by yourself.
By using this powerful barrage library, developers can easily implement functions such as playing, sending, and blocking barrage, and provide a richer interactive experience for scenes such as video and live broadcast.

Fourth, the complete code of the initial implementation

CustomBarrageView.kt

class CustomBarrageView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
    
    

    // 弹幕池
    private var pool: Pools.SimplePool<TextView> = Pools.SimplePool(10)
    //弹幕的数据列表
    private var barrageList = mutableListOf<BarrageItem>()

    fun addBarrage(data: BarrageItem){
    
    
        //1、初始化弹幕子View
        //pool.acquire() 失败之后 就创建一个
        val childView = pool.acquire() ?: TextView(context).apply {
    
    
            text = data.text
            textSize = data.textSize
            setTextColor(ContextCompat.getColor(context, R.color.white))
        }
        //2、测量弹幕子View,也就是measure呗
        childView.measure(measuredWidth, measuredHeight)
        //3、添加弹幕子View
        addView(childView)
        //4、设置弹幕子View的布局,也就是layout呗
        //这里我想让他从右面到左面移动,上下的位置是随机的
        val left = measuredWidth
        val top = nextInt(1, measuredHeight) //kotlin的Random.nextInt函数
        childView.layout(left, top, left + childView.measuredWidth, top + childView.measuredHeight)
        //5、开启弹幕子View动画
        val anim : ValueAnimator = ValueAnimator.ofFloat(1.0f).apply {
    
    
            duration = 7000
            interpolator = LinearInterpolator() //线性的插值器
            addUpdateListener {
    
    
                val value = it.animatedFraction
                val left = (measuredWidth - value *(measuredWidth + childView.measuredWidth)).toInt()
                //通过改变布局来实现弹幕的滚动
                childView.layout(left, top, left + childView.measuredWidth, top + childView.measuredHeight)
            }

            addListener(onEnd = {
    
    
                removeView(childView)
                pool.release(childView)
                barrageList.remove(data)
            })
        }

        anim.start()
    }

    fun addBarrageList(dataList : MutableList<BarrageItem>){
    
    
        this.barrageList = dataList
        dataList.forEach {
    
    
            addBarrage(it)
        }
    }

    override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
    
    

    }
}

BarrageItem.kt

//可以定义图片头像、字体颜色、等等。我用的TextView给大家演示,
//大家可以扩展到自己的自定义弹幕子View上
data class BarrageItem(var text: String, var textSize: Float = 16f)

The effect is as follows:
insert image description here

Summarize

The summary is that the next article is a custom View actual combat "Round Head", and then give me a thumbs up.

Guess you like

Origin blog.csdn.net/weixin_45112340/article/details/131511429