文章目录
一、前言
- 一、前言
- 二、功能
- 三、实例
- 1. 效果图
- 2. 提前准备
- 3. 布局文件
- 4. 初始化弹幕数据
- 5. 创建解析器对象
- 6. 添加文本弹幕
- 7. 添加图文混排弹幕
- 8. 弹幕的隐藏/显示,暂停/继续
- 9. 释放资源
Android上开源弹幕解析绘制引擎项目。
GitHub 地址:DanmakuFlameMaster
二、功能-
使用多种方式(View/SurfaceView/TextureView)实现高效绘制
-
B站xml弹幕格式解析
-
基础弹幕精确还原绘制
-
支持mode7特殊弹幕
-
多核机型优化,高效的预缓存机制
-
支持多种显示效果选项实时切换
-
实时弹幕显示支持
-
换行弹幕支持/运动弹幕支持
-
支持自定义字体
-
支持多种弹幕参数设置
-
支持多种方式的弹幕屏蔽
- 在 build.gradle 中添加如下依赖:
repositories {
jcenter()
}
--------------------------------------------------------------
dependencies {
//弹幕
implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25'
implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'
}
- 弹幕数据文件 可以在 GitHub 上下在资源:
4. 初始化弹幕数据
private void initDanmaku() {
// 设置最大显示行数
HashMap maxLinesPair = new HashMap();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 10); // 滚动弹幕最大显示5行
// 设置是否禁止重叠
HashMap overlappingEnablePair = new HashMap();
overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);
mContext = DanmakuContext.create();
mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3).setDuplicateMergingEnabled(false)
.setScrollSpeedFactor(1.2f)
.setScaleTextSize(1.2f)
.setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer
// .setCacheStuffer(new BackgroundCacheStuffer()) // 绘制背景使用BackgroundCacheStuffer
.setMaximumLines(maxLinesPair)
.preventOverlapping(overlappingEnablePair).setDanmakuMargin(40);
if (mDanmakuView != null) {
mParser = createParser(this.getResources().openRawResource(R.raw.comments));
mDanmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void drawingFinished() {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
// Log.d("DFM", "danmakuShown(): text=" + danmaku.text);
}
@Override
public void prepared() {
loadData();
mDanmakuView.start();
}
});
mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {
@Override
public boolean onDanmakuClick(IDanmakus danmakus) {
Log.d("DFM", "onDanmakuClick: danmakus size:" + danmakus.size());
BaseDanmaku latest = danmakus.last();
if (null != latest) {
Log.d("DFM", "onDanmakuClick: text of latest danmaku:" + latest.text);
return true;
}
return false;
}
@Override
public boolean onDanmakuLongClick(IDanmakus danmakus) {
return false;
}
@Override
public boolean onViewClick(IDanmakuView view) {
// mMediaController.setVisibility(View.VISIBLE);
return false;
}
});
mDanmakuView.prepare(mParser, mContext);
mDanmakuView.showFPS(false);
mDanmakuView.enableDanmakuDrawingCache(true);
}
}
5. 创建解析器对象
/**
* Created on 2022/8/17 11:20
*
* @author Gong Youqiang
*/
public class HuaKeDanmukuParser extends BaseDanmakuParser {
static {
System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
}
protected float mDispScaleX;
protected float mDispScaleY;
@Override
public Danmakus parse() {
if (mDataSource != null) {
AndroidFileSource source = (AndroidFileSource) mDataSource;
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
XmlContentHandler contentHandler = new XmlContentHandler();
xmlReader.setContentHandler(contentHandler);
xmlReader.parse(new InputSource(source.data()));
return contentHandler.getResult();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public class XmlContentHandler extends DefaultHandler {
private static final String TRUE_STRING = "true";
public Danmakus result;
public BaseDanmaku item = null;
public boolean completed = false;
public int index = 0;
public Danmakus getResult() {
return result;
}
@Override
public void startDocument() throws SAXException {
result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator());
}
@Override
public void endDocument() throws SAXException {
completed = true;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
String tagName = localName.length() != 0 ? localName : qName;
tagName = tagName.toLowerCase(Locale.getDefault()).trim();
if (tagName.equals("d")) {
// 我从未见过如此厚颜无耻之猴
// 0:时间(弹幕出现时间)
// 1:类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕)
// 2:字号
// 3:颜色
// 4:时间戳 ?
// 5:弹幕池id
// 6:用户hash
// 7:弹幕id
String pValue = attributes.getValue("p");
// parse p value to danmaku
String[] values = pValue.split(",");
if (values.length > 0) {
long time = (long) (parseFloat(values[0]) * 1000); // 出现时间
int type = parseInteger(values[1]); // 弹幕类型
float textSize = parseFloat(values[2]); // 字体大小
int color = (int) ((0x00000000ff000000 | parseLong(values[3])) & 0x00000000ffffffff); // 颜色
// int poolType = parseInteger(values[5]); // 弹幕池类型(忽略
item = mContext.mDanmakuFactory.createDanmaku(type, mContext);
if (item != null) {
item.setTime(time);
item.textSize = textSize * (mDispDensity - 0.6f);
item.textColor = color;
item.textShadowColor = color = 11) {
endX = parseFloat(textArr[7]);
endY = parseFloat(textArr[8]);
if (!"".equals(textArr[9])) {
translationDuration = parseInteger(textArr[9]);
}
if (!"".equals(textArr[10])) {
translationStartDelay = (long) (parseFloat(textArr[10]));
}
}
if (isPercentageNumber(textArr[0])) {
beginX *= DanmakuFactory.BILI_PLAYER_WIDTH;
}
if (isPercentageNumber(textArr[1])) {
beginY *= DanmakuFactory.BILI_PLAYER_HEIGHT;
}
if (textArr.length >= 8 && isPercentageNumber(textArr[7])) {
endX *= DanmakuFactory.BILI_PLAYER_WIDTH;
}
if (textArr.length >= 9 && isPercentageNumber(textArr[8])) {
endY *= DanmakuFactory.BILI_PLAYER_HEIGHT;
}
item.duration = new Duration(alphaDuraion);
item.rotationZ = rotateZ;
item.rotationY = rotateY;
mContext.mDanmakuFactory.fillTranslationData(item, beginX,
beginY, endX, endY, translationDuration, translationStartDelay, mDispScaleX, mDispScaleY);
mContext.mDanmakuFactory.fillAlphaData(item, beginAlpha, endAlpha, alphaDuraion);
if (textArr.length >= 12) {
// 是否有描边
if (!TextUtils.isEmpty(textArr[11]) && TRUE_STRING.equalsIgnoreCase(textArr[11])) {
item.textShadowColor = Color.TRANSPARENT;
}
}
if (textArr.length >= 13) {
//TODO 字体 textArr[12]
}
if (textArr.length >= 14) {
// Linear.easeIn or Quadratic.easeOut
((SpecialDanmaku) item).isQuadraticEaseOut = ("0".equals(textArr[13]));
}
if (textArr.length >= 15) {
// 路径数据
if (!"".equals(textArr[14])) {
String motionPathString = textArr[14].substring(1);
if (!TextUtils.isEmpty(motionPathString)) {
String[] pointStrArray = motionPathString.split("L");
if (pointStrArray.length > 0) {
float[][] points = new float[pointStrArray.length][2];
for (int i = 0; i = 2) {
points[i][0] = parseFloat(pointArray[0]);
points[i][1] = parseFloat(pointArray[1]);
}
}
mContext.mDanmakuFactory.fillLinePathData(item, points, mDispScaleX,
mDispScaleY);
}
}
}
}
}
}
}
private String decodeXmlString(String title) {
if (title.contains("&")) {
title = title.replace("&", "&");
}
if (title.contains(""")) {
title = title.replace(""", "\"");
}
if (title.contains(">")) {
title = title.replace(">", ">");
}
if (title.contains("<")) {
title = title.replace("<", "
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?