安卓开源代码库_开源代码网站github

安卓开源代码库_开源代码网站githubAndroid上开源弹幕解析绘制引擎项目

在这里插入图片描述

一、前言

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">

    <VideoView android:id="@+id/videoview" android:visibility="gone" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/ic_main_bg"/>

    <ImageView android:src="@mipmap/ic_huake" android:layout_width="240dp" android:layout_height="120dp"/>

    <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="match_parent" layout="@layout/media_controller" />

    <Button android: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="开始抽奖" />

    <Button android: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.BottomItemView android: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() { 
   
                @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")) { 
   
                // <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:弹幕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 <= Color.BLACK ? Color.WHITE : Color.BLACK;
                    }
                }
            }
        }

        @Override
        public 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;
            }
        }

        @Override
        public 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 data
                String 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;
        }
    }

    @Override
    public 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. 释放资源

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

说明:更多使用请参考 sample

今天的文章安卓开源代码库_开源代码网站github分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/85384.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注