文章目录

  • 一、前言
  • 二、功能
  • 三、实例
    • 1. 效果图
    • 2. 提前准备
    • 3. 布局文件
    • 4. 初始化弹幕数据
    • 5. 创建解析器对象
    • 6. 添加文本弹幕
    • 7. 添加图文混排弹幕
    • 8. 弹幕的隐藏/显示,暂停/继续
    • 9. 释放资源

一、前言

Android上开源弹幕解析绘制引擎项目。

GitHub 地址:DanmakuFlameMaster

二、功能

  • 使用多种方式(View/SurfaceView/TextureView)实现高效绘制

  • B站xml弹幕格式解析

  • 基础弹幕精确还原绘制

  • 支持mode7特殊弹幕

  • 多核机型优化,高效的预缓存机制

  • 支持多种显示效果选项实时切换

  • 实时弹幕显示支持

  • 换行弹幕支持/运动弹幕支持

  • 支持自定义字体

  • 支持多种弹幕参数设置

  • 支持多种方式的弹幕屏蔽

三、实例

1. 效果图

2. 提前准备

  • 在 build.gradle 中添加如下依赖:
repositories {jcenter()
}
--------------------------------------------------------------
dependencies {//弹幕implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25'implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'
}
  • 弹幕数据文件
    可以在 GitHub 上下在资源:

3. 布局文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@mipmap/ic_main_bg"tools:context=".MainActivity"><VideoViewandroid:id="@+id/videoview"android:visibility="gone"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@mipmap/ic_main_bg"/><ImageViewandroid:src="@mipmap/ic_huake"android:layout_width="240dp"android:layout_height="120dp"/><master.flame.danmaku.ui.widget.DanmakuViewandroid: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="match_parent"layout="@layout/media_controller" /><Buttonandroid:id="@+id/btn_lottery_start"android:layout_width="200dp"android:layout_height="40dp"android:textSize="24sp"android:layout_gravity="center|bottom"android:layout_marginBottom="40dp"android:textColor="@color/white"android:background="@drawable/xui_config_bg_blue_btn"android:text="开始抽奖" /><Buttonandroid:id="@+id/btn_lottery_stop"android:layout_width="200dp"android:layout_height="40dp"android:textSize="24sp"android:layout_gravity="center|bottom"android:layout_marginBottom="40dp"android:textColor="@color/white"android:visibility="gone"android:background="@drawable/xui_config_bg_blue_btn"android:text="停止抽奖" /><com.hkt.hklottery.widget.BottomItemViewandroid:id="@+id/bottom_exit"android:layout_width="79dp"android:layout_height="wrap_content"android:layout_gravity="bottom"android:layout_margin="20dp"app:bt_img_width="38dp"app:bt_img_height="38dp"app:bt_img_src="@mipmap/more_exit"app:bt_txt_text="退出"/></FrameLayout>

4. 初始化弹幕数据

private void initDanmaku() {// 设置最大显示行数HashMap<Integer, Integer> maxLinesPair = new HashMap<Integer, Integer>();maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 10); // 滚动弹幕最大显示5行// 设置是否禁止重叠HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<Integer, Boolean>();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() {@Overridepublic void updateTimer(DanmakuTimer timer) {}@Overridepublic void drawingFinished() {}@Overridepublic void danmakuShown(BaseDanmaku danmaku) {//                    Log.d("DFM", "danmakuShown(): text=" + danmaku.text);}@Overridepublic void prepared() {loadData();mDanmakuView.start();}});mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {@Overridepublic 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;}@Overridepublic boolean onDanmakuLongClick(IDanmakus danmakus) {return false;}@Overridepublic 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;@Overridepublic 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;}@Overridepublic void startDocument() throws SAXException {result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator());}@Overridepublic void endDocument() throws SAXException {completed = true;}@Overridepublic 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")) {// <d p="23.826000213623,1,25,16777215,1422201084,0,057075e9,757076900">我从未见过如此厚颜无耻之猴</d>// 0:时间(弹幕出现时间)// 1:类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕)// 2:字号// 3:颜色// 4:时间戳 ?// 5:弹幕池id// 6:用户hash// 7:弹幕idString pValue = attributes.getValue("p");// parse p value to danmakuString[] 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 <= Color.BLACK ? Color.WHITE : Color.BLACK;}}}}@Overridepublic void endElement(String uri, String localName, String qName) throws SAXException {if (item != null && item.text != null) {if (item.duration != null) {String tagName = localName.length() != 0 ? localName : qName;if (tagName.equalsIgnoreCase("d")) {item.setTimer(mTimer);item.flags = mContext.mGlobalFlagValues;Object lock = result.obtainSynchronizer();synchronized (lock) {result.addItem(item);}}}item = null;}}@Overridepublic void characters(char[] ch, int start, int length) {if (item != null) {DanmakuUtils.fillText(item, decodeXmlString(new String(ch, start, length)));item.index = index++;// initial specail danmaku dataString text = String.valueOf(item.text).trim();if (item.getType() == BaseDanmaku.TYPE_SPECIAL && text.startsWith("[")&& text.endsWith("]")) {//text = text.substring(1, text.length() - 1);String[] textArr = null;//text.split(",", -1);try {JSONArray jsonArray = new JSONArray(text);textArr = new String[jsonArray.length()];for (int i = 0; i < textArr.length; i++) {textArr[i] = jsonArray.getString(i);}} catch (JSONException e) {e.printStackTrace();}if (textArr == null || textArr.length < 5 || TextUtils.isEmpty(textArr[4])) {item = null;return;}DanmakuUtils.fillText(item, textArr[4]);float beginX = parseFloat(textArr[0]);float beginY = parseFloat(textArr[1]);float endX = beginX;float endY = beginY;String[] alphaArr = textArr[2].split("-");int beginAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[0]));int endAlpha = beginAlpha;if (alphaArr.length > 1) {endAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[1]));}long alphaDuraion = (long) (parseFloat(textArr[3]) * 1000);long translationDuration = alphaDuraion;long translationStartDelay = 0;float rotateY = 0, rotateZ = 0;if (textArr.length >= 7) {rotateZ = parseFloat(textArr[5]);rotateY = parseFloat(textArr[6]);}if (textArr.length >= 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 < pointStrArray.length; i++) {String[] pointArray = pointStrArray[i].split(",");if (pointArray.length >= 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("&amp;")) {title = title.replace("&amp;", "&");}if (title.contains("&quot;")) {title = title.replace("&quot;", "\"");}if (title.contains("&gt;")) {title = title.replace("&gt;", ">");}if (title.contains("&lt;")) {title = title.replace("&lt;", "<");}return title;}}private boolean isPercentageNumber(String number) {//return number >= 0f && number <= 1f;return number != null && number.contains(".");}private float parseFloat(String floatStr) {try {return Float.parseFloat(floatStr);} catch (NumberFormatException e) {return 0.0f;}}private int parseInteger(String intStr) {try {return Integer.parseInt(intStr);} catch (NumberFormatException e) {return 0;}}private long parseLong(String longStr) {try {return Long.parseLong(longStr);} catch (NumberFormatException e) {return 0;}}@Overridepublic BaseDanmakuParser setDisplayer(IDisplayer disp) {super.setDisplayer(disp);mDispScaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH;mDispScaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT;return this;}
}

6. 添加文本弹幕

private void addDanmaku(boolean islive) {BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);if (danmaku == null || mDanmakuView == null) {return;}danmaku.text = "这是一条弹幕" + System.nanoTime();danmaku.padding = 5;danmaku.priority = 0;  //0 表示可能会被各种过滤器过滤并隐藏显示 //1 表示一定会显示, 一般用于本机发送的弹幕danmaku.isLive = islive; //是否是直播弹幕danmaku.time = mDanmakuView.getCurrentTime() + 1200; //显示时间danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f);danmaku.textColor = Color.RED;danmaku.textShadowColor = Color.WHITE; //阴影/描边颜色danmaku.borderColor = Color.GREEN; //边框颜色,0表示无边框mDanmakuView.addDanmaku(danmaku);}

7. 添加图文混排弹幕

private void addDanmaKuShowTextAndImage(boolean islive) {BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);drawable.setBounds(0, 0, 100, 100);SpannableStringBuilder spannable = createSpannable(drawable);danmaku.text = spannable;danmaku.padding = 5;danmaku.priority = 1;  // 一定会显示, 一般用于本机发送的弹幕danmaku.isLive = islive;danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f);danmaku.textColor = Color.RED;danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低danmaku.underlineColor = Color.GREEN;mDanmakuView.addDanmaku(danmaku);}private SpannableStringBuilder createSpannable(Drawable drawable) {String text = "bitmap";SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);ImageSpan span = new ImageSpan(drawable);//ImageSpan.ALIGN_BOTTOM);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;}

8. 弹幕的隐藏/显示,暂停/继续

mDanmakuView.hide();
mDanmakuView.show();
//暂停
if (mDanmakuView != null && mDanmakuView.isPrepared()) {mDanmakuView.pause();}
//继续
if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {mDanmakuView.resume();}

9. 释放资源

@Overrideprotected void onDestroy() {super.onDestroy();if (mDanmakuView != null) {// dont forget release!mDanmakuView.release();mDanmakuView = null;}}

说明:更多使用请参考 sample

【Android -- 开源库】DanmakuFlameMaster 的基本使用相关推荐

  1. Android开源库集合(控件)

    RecycleView: RecycleView功能增强 https://github.com/Malinskiy/SuperRecyclerView RecycleView功能增强(拖拽,滑动删除, ...

  2. 关于Android开源库分享平台,(GitClub)微信小程序的开发体验

    七八月份的深圳一直在下雨,总有人说雨天适合窝在家看书,对于程序开发者来说更是难得的学习机会.我们502工作室的小伙伴利用这个时间学习了一下微信小程序开发,并上线了一个GitClub小程序,目前功能有些 ...

  3. Android 开源库获取途径整理

    最新内容请见原文: http://www.trinea.cn/android/android-open-project-summary/ 介绍目前收藏 Android 开源库比较多的 GitHub 项 ...

  4. Android开源库集合(UI效果)

    动画效果 粒子动画效果 https://github.com/glomadrian/Grav 水波式loading等待动画 https://github.com/race604/WaveLoading ...

  5. android 日历翻页动画,Android开源库合集:轻松实现Android动态,炫目:日历效果...

    前言: 了解过那种动态,炫目的日历效果吗?你知道是怎么 操作的嘛?是否想过,用UI就可以实现,对,也许你说的对,不过UI只是都是动态效果的一部分.那么今天用Annroid开源库,来告诉你android ...

  6. Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

    前言 V- Layout 是阿里出品的基础 UI 框架,用于快速实现页面的复杂布局,在手机天猫 Android版 内广泛使用 让人激动的是,在上个月V- Layout终于在Github上开源! Git ...

  7. GitHub 上排名前 100 的 Android 开源库介绍

    转自:http://www.codeceo.com/article/github-top-100-android-libs.html 本项目主要对目前 GitHub 上排名前 100 的 Androi ...

  8. Android开源库总结

    自己总结的Android开源项目及库. github排名https://github.com/trending, github搜索:https://github.com/search UI Aweso ...

  9. 排名前100的Android开源库

    本项目主要对目前GitHub上排名前100的Android开源库进行简单的介绍,至于排名完全是根据GitHub搜索Java语言选择「BestMatch」得到的结果,然后过滤了跟Android不相关的项 ...

  10. GitHub 上排名前 100 的 Android 开源库进行简单的介绍

    本文转载于:https://github.com/Freelander/Android_Data/blob/master/Android-Librarys-Top-100.md 本项目主要对目前 Gi ...

最新文章

  1. XML DOM – 访问节点概述
  2. 【转】 Android Fragment 真正的完全解析(下)
  3. 信息系统项目管理师:第6章:项目进度管理-章节真题
  4. 吊打一切现有开源OCR项目:效果再升7%,速度提升220%
  5. java spring流程_浅谈SpringMVC执行过程
  6. 微信小程序部分功能介绍和实现
  7. .NET Framework 2.0 组件和非托管代码与交互操作详解(转)
  8. 忽略NVRAM的config,修改cisco密码
  9. [蓝桥杯2019初赛]矩形切割-找规律
  10. 逻辑代数01律的理解_零基础学习计算机原理:布尔逻辑和逻辑门
  11. split函数python_Python numpy.hsplit函数方法的使用
  12. 后端开发都应该掌握的Redis基础
  13. VS2012+OpenCV2.4.9+Qt5.3.1环境配置
  14. 深度学习CNN, R-CNN
  15. IT架构的本质--我的五点感悟
  16. java求职英文简历范本2篇_JAVA英文求职简历范文
  17. 经纬度 距离 mysql_mysql 根据经纬度计算距离并排序
  18. 计算文本相似度的常用算法
  19. java输出到空心三角形_java经典算法_019打印三角形(空心,实心) | 学步园
  20. CF1379C Choosing flowers

热门文章

  1. C# LiveUpdate.exe实现文件在线更新(原理说明,使用指南一)
  2. uni-app 可拖拽-悬浮菜单
  3. cf997C. Sky Full of Stars(组合数 容斥)
  4. webp 项目总结以及思考
  5. STM32F103C8T6驱动6线OLED(SPI通讯)
  6. linux清理oracle磁盘空间,Linux / Unix 下文件删除、句柄 与空间释放问题
  7. 企业会计准则(具体准则)第4号——固定资产
  8. ubuntu18.04下设置软件开机自启动
  9. 文章数据分析与自动分类
  10. Box-Muller 变换