自定义View实战《弹幕》
- 前言
- 一、步骤讲解
- 二、缓存优化
- 三、弹幕框架DanmakuFlameMaster
- 介绍DanmakuFlameMaster
- DanmakuFlameMaster的使用
- 四、初步实现的完整代码
- 总结
前言
前面已经学习了自定义的View《渐变色的文字》是继承View的。现在我们在继承ViewGroup来实现我们的《弹幕》View。
本文分为三部分
1、步骤讲解和分步实战
2、缓存优化
3、三方弹幕框架DanmakuFlameMaster
4、第一步初步实现的完整代码
继承View和ViewGroup实现的View有什么明显的区别吗?
一句话打通你任督二脉:
自定义View主要实现的是onMeasure和onDraw
自定义ViewGroup主要实现的是onMeasure和onLayout
好的现在让我们来实现我们的《弹幕》吧。
一、步骤讲解
实现弹幕听起来复杂其实很简单,不信的话往下看。
步骤:
1、初始化弹幕子View
- 可以自定义弹幕子View(我这里就先用TextView代替了)
- 添加方法可以是单个和多个
fun addBarrage(data: BarrageItem){ //BarrageItem自定义的View的数据实体
//1、初始化弹幕子View
val childView : TextView = TextView(context).apply {
text = data.text
textSize = data.textSize
}
fun addBarrageList(dataList : MutableList<BarrageItem>){
this.barrageList = dataList
dataList.forEach {
addBarrage(it)
}
}
2、测量子View
- 测量的那肯定就是onMeasure了,前面《View的绘制流程》中说过子onMeasure的两个参数widthMeasureSpec和heightMeasureSpec是父View给他的测量信息。
- 这里我弹幕是整个View大小的,XML中用了match_parent,所以就不用写他的onMeasue方法了
//2、测量弹幕子View,也就是measure呗
childView.measure(measuredWidth, measuredHeight)
3、向ViewGroup中添加子View
//3、添加弹幕子View
addView(childView)
4、测量完事了,也添加完了,那就该摆放位置了
- 摆放位置不就是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、开启弹幕滚动的动画
- 那从左到右的动画那岂不是很简单吗
- 可以使用TranslateAnimation相对简单
- 但是后面大家需要扩展一下效果还是用ValueAnimator比较好(透明度、旋转角度、缩放比例)
//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()
完事初版的弹幕就完成了。
完整代码我放在最后吧还是,在中间的话太乱了。
二、缓存优化
实现其实大家一看就都懂了,那么稍微进阶一点那肯定是要优化一下。
那么最明显的优化那肯定是View的绘制消耗的资源了。因为这样实现的话,每发送一条弹幕就意味这要绘制一个弹幕的子View,那肯定是消耗资源的。
有什么方法能减轻压力呢,那通过之前讲解的RecyclerView的缓存我们受到了启发。
1、我们把移除屏幕的弹幕子View不去做销毁,给他缓存起来不就行了吗
2、再需要新的弹幕子View添加的时候,给他复用一下,就会减少绘制,减轻压力了
3、这样就利用了RecyclerView的缓存思想,进行了优化
马上更新()
三、弹幕框架DanmakuFlameMaster
介绍DanmakuFlameMaster
1、弹幕数据的解析:DanmakuFlameMaster支持多种弹幕数据格式,包括ASS、XML、JSON等。它首先根据不同的格式,对弹幕数据进行解析,将其转化为统一的数据结构。
2、弹幕的布局和渲染:通过对解析得到的弹幕数据进行布局,确定每条弹幕在屏幕上的位置和显示时间。然后,使用图像渲染技术,将弹幕绘制到屏幕上。
3、弹幕的控制和交互:DanmakuFlameMaster提供了丰富的弹幕控制和交互功能。用户可以设置弹幕的显示样式、字体颜色、速度等属性。同时,还支持弹幕的暂停、播放、弹幕屏蔽等操作。
4、弹幕的优化和性能提升:为了提高弹幕的渲染效率和用户体验,DanmakuFlameMaster采用了多线程技术和缓存策略。它将弹幕的渲染和显示分离开来,使得弹幕的处理和渲染可以并行进行,从而提高了整体的性能和效率。
总的来说,DanmakuFlameMaster通过对弹幕数据的解析和渲染,以及弹幕的控制和交互,实现了弹幕的高效显示和优化。它在很多弹幕视频网站和应用中被广泛应用。
DanmakuFlameMaster的使用
1、引入DanmakuFlameMaster库
首先,在你的项目的build.gradle文件中添加以下依赖项,以引入DanmakuFlameMaster库:
implementation 'com.github.ctiao:DanmakuFlameMaster:0.8.7'
然后,进行同步操作,确保依赖项成功导入。
2、DanmakuView的基本使用
在你的布局文件中添加DanmakuView控件,并设置相关属性。例如,指定宽高、背景色等:
<su.litvak.danmaku.view.DanmakuView
android:id="@+id/danmaku_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" />
在代码中,获取DanmakuView的实例,并进行基本的配置和初始化操作:
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、解析和添加弹幕数据
DanmakuFlameMaster支持从多种来源解析和添加弹幕数据,包括本地文件、URL、InputStream等。
例如,从本地文件解析和添加弹幕数据:
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();
}
另外,DanmakuFlameMaster还提供了多种配置参数,例如字体、字号、显示区域等,开发者可以根据需要进行自定义设置。
4、控制弹幕的播放和暂停
DanmakuView提供了一些方法,用于控制弹幕的播放和暂停。例如:
danmakuView.start(); // 开始播放弹幕
danmakuView.pause(); // 暂停播放弹幕
danmakuView.resume(); // 恢复播放弹幕
danmakuView.stop(); // 停止播放弹幕
danmakuView.release(); // 释放资源
5、弹幕的发送和屏蔽
DanmakuFlameMaster还支持用户发送弹幕和屏蔽指定类型的弹幕。例如:
发送弹幕:
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);
屏蔽指定类型的弹幕:
// 屏蔽顶部弹幕
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了,DanmakuFlameMaster的使用就讲解到这里,还有很多玩法自己探索吧。
通过使用这个强大的弹幕库,开发者可以轻松实现弹幕的播放、发送和屏蔽等功能,为视频、直播等场景提供更加丰富的交互体验。
四、初步实现的完整代码
CustomBarrageView.kt
class CustomBarrageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
//弹幕的数据列表
private var barrageList = mutableListOf<BarrageItem>()
fun addBarrage(data: BarrageItem){
//1、初始化弹幕子View
val childView : TextView = TextView(context).apply {
text = data.text
textSize = data.textSize
}
//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)
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)
效果如下:
总结
总结就是,后面一篇是自定义View实战《圆形头像》,之后给我点点赞。