Android弹幕实现:基于B站弹幕开源系统(3)-文本弹幕的完善和细节调整

简介: Android弹幕实现:基于B站弹幕开源系统(3)本文在附录1,2的基础上再次对异步获取弹幕并显示弹幕完善逻辑和代码,集中在上层Java代码部分:package zhangphil.


Android弹幕实现:基于B站弹幕开源系统(3)

本文在附录1,2的基础上再次对异步获取弹幕并显示弹幕完善逻辑和代码,集中在上层Java代码部分:

package zhangphil.danmaku;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.model.DanmakuTimer;
import master.flame.danmaku.danmaku.model.IDisplayer;
import master.flame.danmaku.danmaku.model.android.DanmakuContext;
import master.flame.danmaku.ui.widget.DanmakuView;

public class MainActivity extends Activity {

    private DanmakuView mDanmakuView;
    private DanmakuContext mContext;
    private AcFunDanmakuParser mParser;

    private final int MAX_DANMAKU_LINES = 8; //弹幕在屏幕显示的最大行数

    private ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor = null;
    private ConcurrentLinkedQueue<DanmakuMsg> mQueue = null; //所有的弹幕数据存取队列,在这里做线程的弹幕取和存
    private ArrayList<DanmakuMsg> danmakuLists = null;//每次请求最新的弹幕数据后缓存list

    private final int WHAT_GET_LIST_DATA = 0xffa01;
    private final int WHAT_DISPLAY_SINGLE_DANMAKU = 0xffa02;

    private final int BASE_TIME = 400;
    private final int BASE_TIME_ADD = 100;

    //标志文本弹幕的序列号
    //区别不同弹幕
    private static int danmakuTextMsgId = 0;

    private final int[] colors = {Color.RED, Color.YELLOW, Color.BLUE, Color.GREEN, Color.CYAN, Color.DKGRAY};

    private Handler mDanmakuHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                case WHAT_GET_LIST_DATA:
                    mDanmakuHandler.removeMessages(WHAT_GET_LIST_DATA);

                    if (danmakuLists != null && !danmakuLists.isEmpty()) {
                        mQueue.addAll(danmakuLists);
                        danmakuLists.clear();

                        if (!mQueue.isEmpty())
                            mDanmakuHandler.sendEmptyMessage(WHAT_DISPLAY_SINGLE_DANMAKU);
                    }

                    break;

                case WHAT_DISPLAY_SINGLE_DANMAKU:
                    mDanmakuHandler.removeMessages(WHAT_DISPLAY_SINGLE_DANMAKU);
                    displayDanmaku();
                    break;
            }
        }
    };

    /**
     * 弹幕数据封装的类(bean)
     */
    private class DanmakuMsg {
        public String msg;
    }

    private void displayDanmaku() {
        boolean p = mDanmakuView.isPaused();
        //如果当前的弹幕由于Android生命周期的原因进入暂停状态,那么不应该不停的消耗弹幕数据
        //要知道,在这里发出一个handler消息,那么将会消费(删掉)ConcurrentLinkedQueue头部的数据
        if (!mQueue.isEmpty() && !p) {
            DanmakuMsg dm = mQueue.poll();
            if (!TextUtils.isEmpty(dm.msg)) {
                addDanmaku(dm.msg, true);
            }

            mDanmakuHandler.sendEmptyMessageDelayed(WHAT_DISPLAY_SINGLE_DANMAKU, (long) (Math.random() * BASE_TIME) + BASE_TIME_ADD);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        danmakuLists = new ArrayList<>();
        mQueue = new ConcurrentLinkedQueue<>();
        mDanmakuView = (DanmakuView) findViewById(R.id.danmakuView);

        initDanmaku();

        mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        GetDanmakuMessageTask mTask = new GetDanmakuMessageTask();
        //延迟0秒执行,每隔若干秒周期执行一次任务
        mScheduledThreadPoolExecutor.scheduleAtFixedRate(mTask, 0, 5, TimeUnit.SECONDS);

        Button show = (Button) findViewById(R.id.show);
        Button hide = (Button) findViewById(R.id.hide);
        Button sendText = (Button) findViewById(R.id.sendText);
        Button pause = (Button) findViewById(R.id.pause);
        Button resume = (Button) findViewById(R.id.resume);
        Button clear = (Button) findViewById(R.id.clear);

        show.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.show();
            }
        });

        hide.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.hide();
            }
        });

        sendText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //每点击一次按钮发送一条弹幕
                sendTextMessage();
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.pause();
            }
        });

        resume.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDanmakuView.resume();
            }
        });

        clear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clearDanmaku();
            }
        });
    }

    /**
     * 假设该线程任务模拟的就是从网络中取弹幕数据的耗时操作
     * 假设这些弹幕数据序列是有序的。
     */
    private class GetDanmakuMessageTask implements Runnable {
        @Override
        public void run() {
            danmakuLists.clear();

            int count = (int) (Math.random() * 50);
            for (int i = 0; i < count; i++) {
                DanmakuMsg message = new DanmakuMsg();
                message.msg = "弹幕:" + danmakuTextMsgId;
                danmakuLists.add(message);

                danmakuTextMsgId++;
            }

            if (!danmakuLists.isEmpty()) {
                Message msg = mDanmakuHandler.obtainMessage();
                msg.what = WHAT_GET_LIST_DATA;
                mDanmakuHandler.sendMessage(msg);
            }
        }
    }

    /**
     * 驱动弹幕显示机制重新运作起来
     */
    private void resumeDanmaku() {
        if (!mQueue.isEmpty())
            mDanmakuHandler.sendEmptyMessageDelayed(WHAT_DISPLAY_SINGLE_DANMAKU, (int) (Math.random() * BASE_TIME) + BASE_TIME_ADD);
    }

    private void clearDanmaku() {
        if (danmakuLists != null && !danmakuLists.isEmpty()) {
            danmakuLists.clear();
        }

        if (mQueue != null && !mQueue.isEmpty())
            mQueue.clear();

        mDanmakuView.clearDanmakusOnScreen();
        mDanmakuView.clear();
    }

    private void initDanmaku() {
        mContext = DanmakuContext.create();

        // 设置最大显示行数
        HashMap<Integer, Integer> maxLinesPair = new HashMap<>();
        maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, MAX_DANMAKU_LINES); // 滚动弹幕最大显示5行

        // 设置是否禁止重叠
        HashMap<Integer, Boolean> overlappingEnablePair = new HashMap<>();
        overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
        overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);

        //普通文本弹幕也描边设置样式
        //如果是图文混合编排编排,最后不要描边
        mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 10) //描边的厚度
                .setDuplicateMergingEnabled(false)
                .setScrollSpeedFactor(1.2f) //弹幕的速度。注意!此值越小,速度越快!值越大,速度越慢。// by phil
                .setScaleTextSize(1.2f)  //缩放的值
//        .setCacheStuffer(new BackgroundCacheStuffer())  // 绘制背景使用BackgroundCacheStuffer
                .setMaximumLines(maxLinesPair)
                .preventOverlapping(overlappingEnablePair);

        mParser = new AcFunDanmakuParser();
        mDanmakuView.prepare(mParser, mContext);

        //mDanmakuView.showFPS(true);
        mDanmakuView.enableDanmakuDrawingCache(true);

        if (mDanmakuView != null) {
            mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
                @Override
                public void updateTimer(DanmakuTimer timer) {
                }

                @Override
                public void drawingFinished() {

                }

                @Override
                public void danmakuShown(BaseDanmaku danmaku) {
                    //Log.d("弹幕文本", "danmakuShown text=" + danmaku.text);
                }

                @Override
                public void prepared() {
                    mDanmakuView.start();
                }
            });
        }
    }

    private void sendTextMessage() {
        addDanmaku("zhangphil@csdn: " + System.currentTimeMillis(), true);
    }

    private void addDanmaku(CharSequence cs, boolean islive) {
        BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
        if (danmaku == null || mDanmakuView == null) {
            return;
        }

        danmaku.text = cs;
        danmaku.padding = 5;
        danmaku.priority = 0;  // 可能会被各种过滤器过滤并隐藏显示
        danmaku.isLive = islive;
        danmaku.setTime(mDanmakuView.getCurrentTime() + 1200);
        danmaku.textSize = 20f * (mParser.getDisplayer().getDensity() - 0.6f); //文本弹幕字体大小
        danmaku.textColor = getRandomColor(); //文本的颜色
        danmaku.textShadowColor = getRandomColor(); //文本弹幕描边的颜色
        //danmaku.underlineColor = Color.DKGRAY; //文本弹幕下划线的颜色
        danmaku.borderColor = getRandomColor(); //边框的颜色

        mDanmakuView.addDanmaku(danmaku);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mDanmakuView != null && mDanmakuView.isPrepared()) {
            mDanmakuView.pause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
            mDanmakuView.resume();

            //重新启动handler消息机制,触发弹幕显示
            //如果没有这一个方法,那么显示弹幕的机制将失灵(失去驱动)
            //这个方法就是重新激发弹幕显示的handler机制。
            resumeDanmaku();
        }
    }

    private void closeGetDanmakuMessage() {
        if (mScheduledThreadPoolExecutor != null)
            mScheduledThreadPoolExecutor.shutdown();
    }

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

        closeGetDanmakuMessage();
    }

    /**
     * 从一系列颜色中随机选择一种颜色
     *
     * @return
     */
    private int getRandomColor() {
        int i = ((int) (Math.random() * 10)) % colors.length;
        return colors[i];
    }
}
AI 代码解读


代码运行结果如图:


附录:
1,《Android弹幕实现:基于B站弹幕开源系统(1)》链接:http://blog.csdn.net/zhangphil/article/details/68067100
2,《Android弹幕实现:基于B站弹幕开源系统(2)》链接:http://blog.csdn.net/zhangphil/article/details/68114226
3,《Java ConcurrentLinkedQueue队列线程安全操作》链接:http://blog.csdn.net/zhangphil/article/details/65936066

目录
打赏
0
0
0
0
15
分享
相关文章
Android Studio App开发入门之文本输入EditText的讲解及使用(附源码 包括编辑框、焦点变更监听器、文本变化监听器 )
Android Studio App开发入门之文本输入EditText的讲解及使用(附源码 包括编辑框、焦点变更监听器、文本变化监听器 )
395 0
Android Studio入门之图像显示解析及实战(附源码 超详细必看)(包括图像视图、图像按钮、同时展示文本与图像)
Android Studio入门之图像显示解析及实战(附源码 超详细必看)(包括图像视图、图像按钮、同时展示文本与图像)
331 1
|
6月前
|
Android经典实战之OkDownload:一个经典强大的文件下载开源库,支持断点续传
本文介绍的 OkDownload 是一个专为 Android 设计的开源下载框架,支持多线程下载、断点续传和任务队列管理等功能,具备可靠性、灵活性和高性能特点。它提供了多种配置选项和监听器,便于开发者集成和扩展。尽管已多年未更新,但依然适用于大多数文件下载需求。
511 1
|
9月前
|
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
539 0
14. 【Android教程】文本输入框 EditText
14. 【Android教程】文本输入框 EditText
759 2
kotlin安卓开发,如何获取设备的唯一id, 有哪些开源库
在Kotlin的Android开发中,获取设备唯一ID的方法包括不稳定的ANDROID_ID、需要权限的IMEI、使用UUID与SharedPreference结合,以及考虑隐私的Firebase Installations ID和Advertising ID。由于隐私问题和Google Play政策,IMEI和ANDROID_ID不推荐作为长期唯一标识。推荐使用UUID(首次安装时生成并存储),或在涉及广告时使用Advertising ID(需用户同意),而Firebase Installations ID则提供了一种合规的设备标识选项。在选择方法时,必须遵守隐私指南和政策。
Android Studio入门之文本内容、大小、颜色的讲解及实战(附源码 超详细必看)
Android Studio入门之文本内容、大小、颜色的讲解及实战(附源码 超详细必看)
437 1
Android App开发动画特效中插值器和估值器的讲解以及利用估值器实现弹幕动画实战(附源码和演示视频 可直接使用)
Android App开发动画特效中插值器和估值器的讲解以及利用估值器实现弹幕动画实战(附源码和演示视频 可直接使用)
178 0
Android -- 存储卡读取文本
Android -- 存储卡读取文本
102 0

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
    59
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    37
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    6
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
    4
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    24
  • 6
    【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    11
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    1
  • 8
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    8
  • 9
    android studio 学习之一
    2
  • 10
    安卓逆向系列教程 4.1 字符串资源
    5
  • 1
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    49
  • 2
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    28
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    69
  • 4
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    87
  • 5
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29
  • 6
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
    229
  • 7
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
    58
  • 8
    【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    35
  • 9
    【03】优雅草央千澈详解关于APP签名以及分发-上架完整流程-第三篇安卓APP上架华为商店后面的步骤-华为应用商店相对比较麻烦一些-华为商店安卓上架
    52
  • 10
    app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
    79
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等