著论准过秦,作赋拟子虚。这篇文章主要讲述Android弹幕框架 黑暗火焰使相关的知识,希望能为你提供帮助。
笑谈风云,
一语定乾坤。大家好,
我是皖江。
今天我将分享由BiliBili开源的android弹幕框架(
DanmakuFlameMaster)
的学习经验。
【Android弹幕框架 黑暗火焰使】我是将整个框架以model的形式引入项目中的,
这样更方便的观察源码。也可以通过依赖的方式注入进来
dependencies {
compile '
com.github.ctiao:DanmakuFlameMaster:0.5.3'
}
先放一下我要做成的效果图:
文章图片
页面分析
从上图来看, 整个UI分成了三层。最下面是视频层, 中间是弹幕层, 顶层是控制层。现在市场上主流的视频直播软件大多都是这样分层的, 不同的是直播类的话, 可能还会再多一层交互层, 显示签到信息、礼物信息什么的。
既然是分层的话, 我就直接用FrameLayout帧布局来实现了。贴一下我的布局文件:
<
FrameLayout xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:layout_width=
"
match_parent"
android:layout_height=
"
match_parent"
>
<
VideoView
android:id=
"
@
+
id/vv_video"
android:layout_width=
"
match_parent"
android:layout_height=
"
match_parent"
/>
<
master.flame.danmaku.ui.widget.DanmakuView
android:id=
"
@
+
id/sv_danmaku"
android:layout_width=
"
match_parent"
android:layout_height=
"
match_parent"
/>
<
include android:id=
"
@
+
id/media_controller"
android:layout_width=
"
match_parent"
android:layout_height=
"
fill_parent"
layout=
"
@
layout/media_controller"
/>
<
/FrameLayout>
控制层的布局:
<
FrameLayout xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:layout_width=
"
match_parent"
android:layout_height=
"
match_parent"
>
<
LinearLayout
android:gravity=
"
center_vertical"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:layout_gravity=
"
bottom"
android:background=
"
#8acc22dd"
>
<
Button
android:layout_weight=
"
1"
android:id=
"
@
+
id/rotate"
android:layout_width=
"
0dp"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/rotate"
/>
<
Button
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:id=
"
@
+
id/btn_hide"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/hide_danmaku"
/>
<
Button
android:id=
"
@
+
id/btn_show"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/show_danmaku"
/>
<
Button
android:id=
"
@
+
id/btn_pause"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/pause_danmaku"
/>
<
Button
android:id=
"
@
+
id/btn_resume"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/resume_danmaku"
/>
<
Button
android:id=
"
@
+
id/btn_send"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/send_danmaku"
/>
<
Button
android:id=
"
@
+
id/btn_send_image_text"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/send_danmaku_image_text"
/>
<
Button
android:id=
"
@
+
id/btn_send_danmakus"
android:layout_width=
"
0dp"
android:layout_weight=
"
1"
android:layout_height=
"
wrap_content"
android:text=
"
@
string/send_danmakus"
/>
<
/LinearLayout>
<
/FrameLayout>
写完布局, 先写一个初始化的方法:
//设置最大行数
HashMap<
Integer,Integer>
maxLinesPair =
new HashMap<
>
();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL,5);
//滚动弹幕最大显示5行
//设置是否禁止重叠
HashMap<
Integer, Boolean>
overlappingEnablePair =
new HashMap<
>
();
overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);
mDanmakuContext =
DanmakuContext.create();
//初始化上下文
mDanmakuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN,3);
//设置弹幕类型
mDanmakuContext.setDuplicateMergingEnabled(false);
//设置是否合并重复弹幕
mDanmakuContext.setScrollSpeedFactor(1.2f);
//设置弹幕滚动速度
mDanmakuContext.setScaleTextSize(1.2f);
//设置弹幕字体大小
mDanmakuContext.setCacheStuffer(new SpannedCacheStuffer(),mCacheStufferAdapter);
//设置缓存绘制填充器 图文混排使用SpannedCacheStuffer
mDanmakuContext.setMaximumLines(maxLinesPair);
//设置最大行数
mDanmakuContext.preventOverlapping(overlappingEnablePair);
//设置是否禁止重叠
mParser =
createParser(this.getResources().openRawResource(R.raw.comments));
//加载弹幕资源文件
mDvDanmaku.prepare(mParser, mDanmakuContext);
mDvDanmaku.showFPS(true);
mDvDanmaku.enableDanmakuDrawingCache(true);
再贴一下绘制填充器的实现, 主要实现了将图片和文字排列在一起的效果
private SpannableStringBuilder createSpannable(Drawable drawable) {
String text =
"
bitmap"
;
SpannableStringBuilder spannableStringBuilder =
new SpannableStringBuilder(text);
ImageSpan span =
new ImageSpan(drawable);
spannableStringBuilder.setSpan(span, 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
spannableStringBuilder.append("
图文混排"
);
spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("
#8A2233B1"
)), 0, spannableStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
return spannableStringBuilder;
}
在BaseDanmaku这个类下, 定义了弹幕的基本运动形式: TYPE_SCROLL_RL、 TYPE_SCROLL_LR、TYPE_FIX_TOP、TYPE_FIX_BOTTOM和TYPE_SPECIAL。也就是从右入左出、左入右出( 逆向弹幕) 、顶部弹幕、底部弹幕以及高级弹幕。( 除此之外还有脚本弹幕)
在初始化的时候, 需要传入一个特有的弹幕上下文DanmukuContext, 通过上下文来初始化一些设置。弹幕资源是保存在xml文件下的, 大致格式如下:
.
<
i>
<
chatserver>
chat.bilibili.com<
/chatserver>
<
chatid>
2962351<
/chatid>
<
mission>
0<
/mission>
<
maxlimit>
1500<
/maxlimit>
<
source>
k-v<
/source>
<
d p=
"
145.91299438477,1,25,16777215,1422201001,0,D6673695,757075520"
>
我从未见过如此厚颜无耻之人<
/d>
<
/i>
头信息不需要太多关注, 来看看d标签下p对应参数的具体意义:
第一个: 弹幕出现的时间
第二个: 弹幕类型( 1、从右至左; 6、从左至右; 5、顶部弹幕; 4、底部弹幕; 7、高级弹幕; 8、脚本弹幕’)
第三个: 字号
第四个: 颜色
第五个: 时间戳
第六个: 弹幕池ID
第七个: 用户hash值
第八个: 弹幕id
以下是弹幕具体解析代码
/**
* 从弹幕文件中提起弹幕
* @
param stream
* @
return
*/
private BaseDanmakuParser createParser(InputStream stream) {
if (stream =
=
null) {
return new BaseDanmakuParser() {
@
Override
protected Danmakus parse() {
return new Danmakus();
}
};
}
ILoader loader =
DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI);
//创建一个BiliDanmakuLoader实例来加载弹幕流文件
try {
loader.load(stream);
} catch (IllegalDataException e) {
e.printStackTrace();
}
BaseDanmakuParser parser =
new BiliDanmukuParser();
//弹幕解析者
IDataSource<
?>
dataSource =
loader.getDataSource();
parser.load(dataSource);
return parser;
}
具体解析方案:
String tagName =
localName.length() !=
0 ? localName : qName;
tagName =
tagName.toLowerCase(Locale.getDefault()).trim();
if (tagName.equals("
d"
)) {
String pValue =
attributes.getValue("
p"
);
// parse p value to danmaku
String[] values =
pValue.split("
,"
);
if (values.length >
0) {
long time =
(long) (Float.parseFloat(values[0]) * 1000);
// 出现时间
int type =
Integer.parseInt(values[1]);
// 弹幕类型
float textSize =
Float.parseFloat(values[2]);
// 字体大小
int color =
Integer.parseInt(values[3]) | 0xFF000000;
// 颜色
item =
mContext.mDanmakuFactory.createDanmaku(type, mContext);
if (item !=
null) {
item.setTime(time);
item.textSize =
textSize * (mDispDensity - 0.6f);
item.textColor =
color;
item.textShadowColor =
color <
=
Color.BLACK ? Color.WHITE : Color.BLACK;
}
}
}
弹幕资源加载完毕后, 就调用mDvDanmuku的prepare( ) 方法, 执行准备。当准备完毕的时候, 就会调用DrawHandler.CallBack()回调中的prepared方法。然后在这个prepared方法中正式让弹幕启动。调用顺序如下:
mDvDanmaku.prepare(mParser, mDanmakuContext);
//传入解析完成的弹幕和上下文
然后执行DanmukuView下的prepare()方法
private void prepare() {
if (handler =
=
null)
handler =
new DrawHandler(getLooper(mDrawingThreadType), this, mDanmakuVisible);
//创建一个Handler
}
通过这个Handler来实现进程间的通讯
handler.setConfig(config);
handler.setParser(parser);
handler.setCallback(mCallback);
handler.prepare();
-》会让handler发送一个message去执行正真的准备
DrawHandler中有一个回调
public interface Callback {
public void prepared();
public void updateTimer(DanmakuTimer timer);
public void danmakuShown(BaseDanmaku danmaku);
public void drawingFinished();
}
真正的准备
mTimeBase =
SystemClock.uptimeMillis();
if (mParser =
=
null || !mDanmakuView.isViewReady()) {//没有准备好,
延时0.1秒后再执行
sendEmptyMessageDelayed(PREPARE, 100);
} else {
prepare(new Runnable() {
@
Override
public void run() {
pausedPosition =
0;
mReady =
true;
if (mCallback !=
null) {
mCallback.prepared();
}
}
});
}
以上是弹幕View的启动调用流程。
那么, 怎么添加弹幕捏? 元芳, 你怎么看? ( TM我怎么知道我怎么看) 看下面
private void addDanmaku(boolean islive) {
BaseDanmaku danmaku =
mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
//添加一条从右到左的滚动弹幕
if (danmaku =
=
null || mDvDanmaku =
=
null) {
return;
}
danmaku.text =
"
这是一条弹幕"
+
System.nanoTime();
danmaku.padding =
5;
danmaku.priority =
0;
// 可能会被各种过滤器过滤并隐藏显示设置为1的话,
就一定会显示适用于本机发送的弹幕
danmaku.isLive =
islive;
danmaku.setTime(mDvDanmaku.getCurrentTime() +
1200);
danmaku.textSize =
25f * (mParser.getDisplayer().getDensity() - 0.6f);
danmaku.textColor =
Color.RED;
//默认设置为红色字体
danmaku.textShadowColor =
Color.WHITE;
danmaku.borderColor =
Color.GREEN;
//为了区别其他弹幕与自己发的弹幕,
再给自己发的弹幕加上边框
mDvDanmaku.addDanmaku(danmaku);
}
以上, 就是 黑暗火焰使基本使用方法。
推荐阅读
- Android研发安全2-Activity组件安全(下)
- Android集合中对象排序
- Android Tombstone 分析
- Android学习---- 十月
- 你可能不知道的 Android Studio 小技巧之「多行编辑」
- Android开发艺术探索——第二章(IPC机制(中))
- Android学习笔记--实现正在加载圆圈,加完完成自动取消
- Android性能优化之巧用软引用与弱引用优化内存使用
- 从高处理解android与服务器交互(看懂了做开发就会非常的容易)