1. 程式人生 > >Android彈幕實現:基於B站彈幕開源系統(3)-文字彈幕的完善和細節調整

Android彈幕實現:基於B站彈幕開源系統(3)-文字彈幕的完善和細節調整



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("
[email protected]
: " + 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]; } }


程式碼執行結果如圖:

相關推薦

Android實現基於B開源系統3-文字完善細節調整

Android彈幕實現:基於B站彈幕開源系統(3)本文在附錄1,2的基礎上再次對非同步獲取彈幕並顯示彈幕完善邏輯和程式碼,集中在上層Java程式碼部分:package zhangphil.danmaku; import android.app.Activity; imp

MySQL系列基於binlog的增量訂閱與消費

clas 需要 val tro ali cat tor rip 變化   在一些業務場景中,像在數據分析中我們有時候需要捕獲數據變化(CDC);在數據審計中,我們也往往需要知道數據從這個點到另一個點的變化;同樣在實時分析中,我們有時候需要看到某個值得實時變化等。 要解決以上

基於百度地圖SDKElasticsearch GEO查詢的地理圍欄分析系統3-前端實現

方便 復制 類型 復制代碼 自動跳轉 rar 窗口 stack delete 轉載自:http://www.cnblogs.com/Auyuer/p/8086975.html MoonLight可視化訂單需求區域分析系統實現功能:   在現實生活中,計算機和互聯網迅速發展,

基於springMVC、Java web、Mysql的B/S人口資訊管理系統

    經過不斷努力,環境基本搭建好了,由於接近1年多時間沒有碰Eclipse好多都忘記了,進度可能會有點慢。以及第一次接觸SpringMVC好多東西都不清楚,不過應該問題不大。鑑於網路的問題,現在還在下載MySQL,大致講一下環境已經已經做了的工作 。     JDK1.

Android自己定義組件系列【6】——進階實踐3

err ack XML @+ layout apk get ast edi 上一篇《Android自己定義組件系列【5】——進階實踐(2)》繼續對任老師的《可下拉的PinnedHeaderExpandableListView的實現》進行了分析,這一篇計劃中間插一段“知識點

OSGi是什麽Java語言的動態模塊系統

平臺 使用 數據 osgi servle http cto 優點 重啟 OSGi是什麽 OSGi亦稱做Java語言的動態模塊系統,它為模塊化應用的開發定義了一個基礎架構。OSGi容器已有多家開源實現,比如Knoflerfish、Equinox和Apache的Felix。您可

基於SSM框架的博客系統博主登錄功能

erl 映射 import incr pac 加密 actor gets AC 一、 準備 1.數據庫 創建表db_blogger: 1 DROP TABLE IF EXISTS `t_blogger`; 2 3 CREATE TABLE `t_bl

20180813視頻筆記 深度學習基礎上篇1之必備基礎知識點 深度學習基礎上篇2神經網絡模型視頻筆記深度學習基礎上篇3神經網絡案例實戰 深度學習基礎下篇

計算 概念 人臉識別 大量 png 技巧 表現 lex github 深度學習基礎上篇(3)神經網絡案例實戰 https://www.bilibili.com/video/av27935126/?p=1 第一課:開發環境的配置 Anaconda的安裝 庫的安裝 Windo

基於ASP.NET的新聞管理系統效果展示

後臺 family 技術 .net image 密碼 src 管理系 修改密碼 5. 新聞管理系統開發與實現 5.1前臺新聞頁面 主頁面 新聞欄展示新聞 搜索新聞 菜單欄鏈接新聞 後臺登錄界面 大管理員後臺管理界面 小管理員後臺管理界面 修改密

Android輸入系統輸入事件傳遞流程InputManagerService的誕生

本文首發於微信公眾號「劉望舒」 原文連結 : Android輸入系統的事件傳遞流程和IMS的誕生 相關文章 解析WMS系列 View體系系列 前言 很多同學可能會認為輸入系統是不是和View的事件分發有些關聯,確實是有些關聯,只不過View事件分發只能算是輸入系統事件傳遞的一部分。這個系列講的

Android輸入系統IMS的啟動過程輸入事件的處理

本文首發於 劉望舒的部落格 地址:liuwangshu.cn/framework/i… 關聯絡列 解析WMS系列 深入理解JNI系列 輸入系統系列 前言 在上一篇文章中,我們學習了IMS的誕生(建立),IMS建立後還會進行啟動,這篇文章我們來學習IMS的啟動過程和輸入事件的處理。 1.IMS

Android輸入系統InputReader的加工型別InputDispatcher的分發過程

關聯絡列 解析WMS系列 深入理解JNI系列 輸入系統系列 前言 在上一篇文章中,我們學習了輸入事件的處理,輸入事件會交由InputDispatcher進行分發,那麼InputDispatcher是如何進行分發的?這篇文章會給你答案。 1.InputReader的加工型別 在Android輸入系統(二

vue從入門到女裝??從零開始搭建後臺管理系統安裝框架

安裝及執行都是基於node的,不會node的可以自行百度,網上教程很多,也不難 專案效果預覽: demo1 demo2 原始碼下載 開始安裝框架: vue element-ui   注意如果報錯安裝失敗就重新安裝,不然雖然本地有element的依賴包但是可能會出一些奇怪的錯誤 另外element-ui

基於MFC的USB上位機開發3資料傳輸模組

延伸閱讀: 基於MFC的USB上位機開發(1)概述 基於MFC的USB上位機開發(2)速度測試模組 基於MFC的USB上位機開發(3)資料傳輸模組 基於MFC的USB上位機開發(4)環路模組 基於MFC的USB上位機開發(5)下環路模組 目錄 1. 設計思路 1.

Unity3D自學筆記——UGUI揹包系統物品出提示框

物品彈出提示框 效果圖 利用Toggle實現物品選中效果 向背包裡層pnlGrid新增一個Toggle Group 修改Item預製體,將其修改為Toggle 向Item新增Toggle元件 預設Is On不選中 其

基於Python檢索系統3分詞後建立資料結構

        分詞應用的是Jieba分詞工具,將爬取得到的新聞進行中文分詞,也就是為了得到以後檢索要用到的關鍵詞。我們給每個關鍵詞建立一個單獨的索引,引入間接桶,應用倒排索引的方法實現最終的結果。

Yii 2.0 搭建基於RBAC許可權的後臺管理系統

三、RBAC整合AdminLTE後臺主題對選單進行控制1.  配置APACHE虛擬主機在 <XAMPP安裝路徑>\apache\conf\extra\httpd-vhosts.conf檔案末尾追加<VirtualHost *:80>     Serve

新一代基於大資料的管理資訊系統MIS報表需求開發

需求: 1.從後臺資料庫,通過spark連線hadoop(大量資料) 2.然後通過將資料在後臺(主要使用java)封裝成前臺需要的格式(一般是json格式),這一步中包含了service,DAO,spring配置以及使用,struts2的配置以及使用,如sql部分過於複雜

揹包系統各個部分的整合整體功能的實現

一、揹包的拾取功能: 在獲得一個新物品時,需要把該物品放到揹包中進行分配,這時候就會出現以下幾種情況: (1)如果已有該物體:該物體的num+1; (2)如果沒有該物體,則查詢是否有空格:                若有空格,將該物體放在有的第一個編號的空格里。在當

Yii 2.0 搭建基於RBAC許可權的後臺管理系統

自己學習Yii 2.0搭建RBAC的一些筆記整理了一下發來和大家分享下,大神勿噴。一、安裝PHP依賴管理工具 1. 配置PHP證書確保可以使用php訪問http下載 ca-bundle.crt和cacert.pem將這兩個檔案放在<XAMPP的安裝路徑>/<