I haven’t written blog for a long time, but also because I am very busy with the company project recently! I just have a little time after this iteration, so I’m blogging to kill some time! Now many live broadcast software have corresponding bullet screen function, and I didn’t pay much attention to it before. Recently, the content about bullet screen was used in the company’s project, so it’s a good time to record relevant knowledge here!

Link to DanmakuFlameMaster barrage at station B

Knowledge of this article

  • DanmakuFlameMaster integration with simple use
  • Advanced use of DanmakuFlameMaster

1. Integration and simple use of DanmakuFlameMaster

Actually I this person is really very stupid, study this at the beginning of time, looked for a lot of articles on the net! But I didn’t understand it very much. Basically, I pasted the content from gitHub directly, and then I knew how to understand it. Perhaps he is too stupid!

1.1 integration of DanmakuFlameMaster

This problem is quite simple, there is nothing to say, according to github above integration can be! Don’t add the bottom two lines if you don’t need x86 and ARMV5 compatibility!

repositories {
    jcenter()
}

dependencies {
    compile 'com. Making. Ctiao: DanmakuFlameMaster: 0.9.25'
    compile 'com. Making. Ctiao: ndkbitmap - armv7a: 0.9.21'

    # Other ABIs: optional # Other ABIs: optional # Other ABIs: optional
    compile 'com. Making. Ctiao: ndkbitmap - armv5:0.9.21'
    compile 'com. Making. Ctiao: ndkbitmap - x86:0.9.21'
}
Copy the code

1.2 simple use of DanmakuFlameMaster

At the beginning of the need to set the content is still a lot of, we will explain one by one!

1.2.1 Layout file

DanmakuFlameMaster using a variety of ways (View/SurfaceView/TextureView) efficient rendering! Which correspond (DanmakuView/DanmakuSurfaceView/DanmakuTextureView) related to the View, due to the integration of the project is the simplest barrage function, all no use about SurfaceView class barrage control!

<? xml version="1.0" encoding="utf-8"? > <android.support.constraint.ConstraintLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="addDanmaku"
        android:text="Add barrage"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <master.flame.danmaku.ui.widget.DanmakuView
        android:id="@+id/dv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn"
        app:layout_constraintVertical_weight="1" />

</android.support.constraint.ConstraintLayout>
Copy the code

Layout is basically looking at the requirements in your project to determine where they fit in! Nothing to say!!

1.2.2 Setting corresponding Properties

Some simple configuration, are DEMO above some, annotation write basically very clear, there is no too much to say!

// Set the maximum number of lines to display HashMap<Integer, Integer> maxLInesPair = new HashMap<>(16); maxLInesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 8); HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<>(16); overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL,true);
    overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true); MContext = danmakucontext.create (); / / set up some related configuration mContext setDuplicateMergingEnabled (falseSetScrollSpeedFactor (1.2f) setScaleTextSize(1.2f) setScaleTextSize(1.2f) setScaleTextSize(1.2f) SetCacheStuffer (new MyCacheStuffer(mActivity)), MBackgroundCacheStuffer) // Sets the maximum number of lines to display. SetMaximumLines (maxLInesPair) Null means that can be overlapped. BaseDanmakuParser defaultDanmakuParser = getDefaultDanmakuParser();Copy the code

In fact, when we first used this in our project, all bullets were returned directly from the server. So at first, my idea was to go through the parser, but THEN I gave up! Why is that? First of all, the rules of JSON parsing are very complicated. I briefly looked at the code. To be honest, MY ability was limited and I really didn’t understand the corresponding JSON structure. I didn’t think this was necessary, so I started a thread and added the corresponding barrage! Isn’t that clever… But even so, you can’t skip the step of setting up the parser!

    public static BaseDanmakuParser getDefaultDanmakuParser() {
        return new BaseDanmakuParser() {
            @Override
            protected IDanmakus parse() {
                returnnew Danmakus(); }}; }Copy the code

Since the parser doesn’t work here, I just wrote a parser the simplest way possible! The code looks like this:

Basically, the above covers all the configuration content about the barrage! SetCacheStuffer () is something I’ll talk about later! Here you know there is such a thing on the line, it is mainly dealing with non-text type bullet screen! So you don’t have to set this thing if you are pure text! More on this later!

1.2.3 Start the corresponding barrage

When the barrage is ready, you can directly adjust the corresponding startup method.

if(mDanmakuView ! = null) { BaseDanmakuParser defaultDanmakuParser = getDefaultDanmakuParser(); / / the corresponding back off mDanmakuView. SetCallback (new master. The flame. Danmaku. Controller. DrawHandler.Callback() {@override public void updateTimer(DanmakuTimer timer) {@override public void updateTimerdrawingFinishedPublic void danmakuShown(BaseDanmaku danmaku) {public void danmakuShown(BaseDanmaku danmaku) {public void danmakuShown(BaseDanmaku danmaku)preparedMdanmakuview.start (); }}); mDanmakuView.prepare(defaultDanmakuParser, mContext); mDanmakuView.enableDanmakuDrawingCache(true);
}
Copy the code

There are also associated lifecycle methods that must be set! Important things three times, three times, three times!

    @Override
    protected void onPause() {
        super.onPause();
        if(mDanmakuView ! = null && mDanmakuView.isPrepared()) { mDanmakuView.pause(); } } @Override protected voidonResume() {
        super.onResume();
        if(mDanmakuView ! = null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { mDanmakuView.resume(); } } @Override protected voidonDestroy() {
        super.onDestroy();
        if(mDanmakuView ! = null) { // dont forget release! mDanmakuView.release(); mDanmakuView = null; } } @Override public voidonBackPressed() {
        super.onBackPressed();
        if (mDanmakuView != null) {
            // dont forget release!
            mDanmakuView.release();
            mDanmakuView = null;
        }
    }
Copy the code

At this point you will find that your screen has nothing, that’s right! Why is that? Because you haven’t added the barrage yet!! With all the preparations ready, let’s start adding bullets! According to the instructions of station B, we configure the relevant barrage like this

Private void addDanmaku(Boolean islive) {// Create a danmaku object. BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);if (danmaku == null || mDanmakuView == null) {
            return; } // Danmaku.text ="This is a barrage."+ System.nanoTime(); Danmaku.padding = 5; // Set the corresponding margin. Danmaku.padding = 5; // It may be filtered by various filters and hidden. If it is a barrage sent by the machine, it is recommended to set it to 1. danmaku.priority = 0; // Whether live danmaku.isLive = isLive; danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); // set the textSize danmaku.textSize = 25f; // set the textColor danmaku.textcolor = color.red; TextShadowColor = color.white; // Set the shadow Color danmaku.textShadowColor = color.white; // danmaku.underlineColor = Color.GREEN; // Set the background Color danmaku.borderColor = color.green; Mdanmakuview.adddanmaku (danmaku); }Copy the code

This successfully sent a text barrage, here is a question, at the beginning, I was wondering when to add a barrage to the page? Since there is no button to add a barrage, the only way to do so is in mdanMakuview.start (); After the call, can not be called in the life cycle method, if it is called in the life cycle method, there will be empty bullets, can not add the problem! Remember… So the whole process works!

2. Advanced use of DanmakuFlameMaster

2.1 Implement the display of customized bullets

The above display, generally only used in the content of live video, but for the kind of bullet screen with a picture! For example, the product comes up and says! Or add an avatar! Add some more words to make it look good! Like this:

When I first looked on the Internet, many people said to use SpannableStringBuilder to implement this content, but I think it is very painful to use SpannableStringBuilder to implement this content! It can also be extremely difficult, especially if the style is a bit more complex! Let’s talk about the implementation of this content!

Remember that there is a setting for graphics? BaseCacheStuffer (BaseCacheStuffer cacheStuffer, BaseCacheStuffer.Proxy cacheStufferAdapter) Since this is the style I want to implement in the project, I carefully researched the display scheme of this style!

It’s actually very simple! Just overriding some methods of the BaseCacheStuffer class! How do you do that? In fact, it is their own draw each bullet screen displayed content, here should be a strategy mode implementation, interested in children’s shoes can have a look! Here is a test of the use of some Canvas API! Not the children’s shoes can baidu! All right, we’ve been rambling for so long! Let’s get started!

I mentioned at the beginning that **setCacheStuffer(BaseCacheStuffer cacheStuffer, Basecachestuffer.proxy cacheStufferAdapter)** This is the way to implement non-literal! So as long as you understand the above two parameters!

  • Parameter 1: you can interpret the corresponding processing as drawing
  • Parameter 2: You can think of it as a callback drawn accordingly

Let’s deal with it one by one:

2.1.1 To achieve the corresponding drawing

Draw the corresponding operation is mainly to achieve BaseCacheStuffer this abstract class, all about the drawing method is your own implementation! So I’d better understand the corresponding Canvas class before I do this!! When you inherit this abstract class, you must implement three corresponding methods:

public class MyCacheStuffer extends BaseCacheStuffer { @Override public void measure(BaseDanmaku danmaku, TextPaint paint, Boolean fromWorkerThread) {// Measure the corresponding method} @override public voidclearCaches} @override public void drawDanmaku(BaseDanmaku danmaku, Canvas Canvas,float left, floatTop, Boolean fromWorkerThread, AndroidDisplayer DisplayerConfig DisplayerConfig) {/ / draw the corresponding method}}Copy the code

Basically is the above three methods, the most important is the measurement and drawing of the two methods! The following is the implementation of a display above!

Public class MyCacheStuffer extends BaseCacheStuffer {/** ** Right hand spacing */ privatefloatRIGHTMARGE; /** * Text and head spacing */ privatefloatLEFTMARGE; /** * private int TEXT_RIGHT_PADDING; /** * text size */ privatefloatTEXT_SIZE; /** * the size of the avatar */ privatefloatIMAGEHEIGHT; Public MyCacheStuffer(Activity Activity) {// Initialize a fixed parameter, These parameters can be customized to your needs LEFTMARGE = active.getResources ().getDimension(r.dimen.dimen_13px); RIGHTMARGE = activity.getResources().getDimension(R.dimen.DIMEN_22PX); IMAGEHEIGHT = activity.getResources().getDimension(R.dimen.DIMEN_60PX); TEXT_SIZE = activity.getResources().getDimension(R.dimen.DIMEN_24PX); } @override public void measure(BaseDanmaku danmaku, TextPaint, Boolean fromWorkerThread) { Object> map = (Map<String, Object>) danmaku.tag; String content = (String) map.get("content");
        Bitmap bitmap = (Bitmap) map.get("bitmap"); // Set paint. SetTextSize (TEXT_SIZE); // Calculate the length of the name and content, take the maximumfloatcontentWidth = paint.measureText(content); Danmaku.paintwidth = contentWidth + IMAGEHEIGHT + LEFTMARGE + RIGHTMARGE; // Set the width of danmaku.paintwidth = contentWidth + IMAGEHEIGHT + LEFTMARGE + RIGHTMARGE; // Set the height of the barrage area danmaku.paintheight = IMAGEHEIGHT * 2; } @Override public voidclearCaches() {

    }

    @Override
    public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, floatTop, Boolean fromWorkerThread, AndroidDisplayer DisplayerConfig DisplayerConfig) {/ / initialized data Map < String, Object> map = (Map<String, Object>) danmaku.tag; String content = (String) map.get("content");
        Bitmap bitmap = (Bitmap) map.get("bitmap");
        String color = (String) map.get("color"); Paint = new Paint(); paint.setTextSize(TEXT_SIZE); // Draw background int textLength = (int) paint.measureText(content); SetColor (color.parsecolor (Color)); // Get the width of the imagefloat rectBgLeft = left;
        float rectBgTop = top;
        float rectBgRight = left + IMAGEHEIGHT + textLength + LEFTMARGE + RIGHTMARGE;
        floatrectBgBottom = top + IMAGEHEIGHT; canvas.drawRoundRect(new RectF(rectBgLeft, rectBgTop, rectBgRight, rectBgBottom), IMAGEHEIGHT / 2, IMAGEHEIGHT / 2, paint); // Draw the avatarfloat avatorRight = left + IMAGEHEIGHT;
        floatavatorBottom = top + IMAGEHEIGHT; canvas.drawBitmap(bitmap, null, new RectF(left, top, avatorRight, avatorBottom), paint); // Draw the content of the barrage, with the text WHITE paint. SetColor (color.white);floatcontentLeft = left + IMAGEHEIGHT + LEFTMARGE; // Calculate the offset of the text paint.fontMetrics FontMetrics = paint.getFontMetrics(); // Is the distance between the baseline and the border on the font, which is top in the figure abovefloattextTop = fontMetrics.top; // Is the distance from the baseline to the bottom border of the font, the bottom in the figure abovefloat textBottom = fontMetrics.bottom;

        floatcontentBottom = top + IMAGEHEIGHT / 2; Int baseLineY = (int) (contentBottom-textTop / 2 - textBottom / 2); DrawText (content, contentLeft, baseLineY, paint); }}Copy the code

I forgot to mention: I think there’s one clever thing about this, and that’s the Tag setup! You can bring in a lot of parameters, nice idea, but I didn’t think of it! I also borrowed from others… A song “shame on yourself” –> give yourself!

There are also changes when you add a barrage, but the changes are minor, like below!!

/ / create a barrage BaseDanmaku danmaku = mContext. MDanmakuFactory. CreateDanmaku (BaseDanmaku. TYPE_SCROLL_RL);if (danmaku == null || mDanmakuView == null) {
        return; } / / set the corresponding data Bitmap showBitmap = BitmapFactory. DecodeResource (getResources (), R.m. Ipmap ic_launcher); showBitmap = BitmapUtils.getShowPicture(showBitmap); Map<String, Object> map = new HashMap<>(16); map.put("content"."Here's what's displayed.");
    map.put("bitmap", showBitmap);
    Random random = new Random();
    int randomNum = random.nextInt(mContentColorBg.length);
    map.put("color", mContentColorBg[randomNum]); // Set the corresponding tag danmaku.tag = map; danmaku.textSize = 0; danmaku.padding = 10; danmaku.text =""; Priority = 1; danmaku.priority = 1; danmaku.isLive =false; danmaku.setTime(mDanmakuView.getCurrentTime()); danmaku.textColor = Color.WHITE; Danmaku.textshadowcolor =0; danmaku.textShadowColor =0; // Add a mdanMakuview.addDanMaku (danmaku);Copy the code

This successfully set up a corresponding barrage! One problem I find is that when you do this, the logic of sending text is reworked! In fact, it is more than a definition of a type, according to different types of different draw good! Very easy to solve! I’m not going to expand here!

2.2 Corresponding monitoring problems

About listening, in fact, is to implement the corresponding method, but it is necessary to explain, how to deal with!

 mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() {@override public Boolean onmakuclick (IDanmakus danmakus) {BaseDanmaku latest = danmakus.last();if(null ! = latest) { Map<String, Object> map = (Map<String, Object>) latest.tag; String userId = (String) map.get(String userId = (String) map.get("content");
                        return true;
                    }
                    return false; } @override public Boolean onDanmakuLongClick(IDanmakus danmakus)return false; } @override public Boolean onViewClick(IDanmakuView) {Override public Boolean onViewClick(IDanmakuView) { guessreturn false; }}); }Copy the code

Gorgeous dividing line


Basically solved the problems in our project, in fact, there are many problems I did not deal with, here is just to give you a simple case, if there is anything wrong still hope to point out! I timely modify… If have what don’t understand, also can leave a message! I will try to help you solve it!!

Forgot, attach the code address