Android弹幕实现:基于B站弹幕开源系统(2)
在附录1的基础上,模拟实现一种实际开发的应用场景:从网络中不间断的周期取弹幕数据,这些弹幕数据往往是批量的,然后把这些从网络中取到的批量数据逐个的显示出来。注意本例中的Handler和线程安全队列ConcurrentLinkedQueue的使用。
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.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
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 ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor = null;
private ConcurrentLinkedQueue mQueue = null;
private final int WHAT_GET_LIST_DATA = 0xffa01;
private final int WHAT_DISPLAY_SINGLE_DANMAKU = 0xffa02;
private final int[] colors = {Color.RED, Color.YELLOW, Color.BLUE, Color.GREEN, Color.CYAN, Color.DKGRAY};
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT_GET_LIST_DATA:
handler.removeMessages(WHAT_GET_LIST_DATA);
ArrayList lists = (ArrayList) msg.obj;
if (lists != null && !lists.isEmpty()) {
mQueue.addAll(lists);
if (!mQueue.isEmpty())
handler.sendEmptyMessage(WHAT_DISPLAY_SINGLE_DANMAKU);
}
break;
case WHAT_DISPLAY_SINGLE_DANMAKU:
handler.removeMessages(WHAT_DISPLAY_SINGLE_DANMAKU);
displayDanmaku();
break;
}
}
};
private void displayDanmaku() {
String s = mQueue.poll();
if (!TextUtils.isEmpty(s)) {
addDanmaku(s, true);
}
if (!mQueue.isEmpty())
handler.sendEmptyMessageDelayed(WHAT_DISPLAY_SINGLE_DANMAKU, (long) (Math.random() * 400) + 100);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mQueue = new ConcurrentLinkedQueue<>();
mDanmakuView = (DanmakuView) findViewById(R.id.danmakuView);
init();
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() {
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayList danmakuLists = new ArrayList<>();
int count = (int) (Math.random() * 100);
for (int i = 0; i < count; i++) {
danmakuLists.add("批量数据" + i + " - " + Math.random());
}
if (!danmakuLists.isEmpty()) {
Message msg = handler.obtainMessage();
msg.what = WHAT_GET_LIST_DATA;
msg.obj = danmakuLists;
handler.sendMessage(msg);
}
}
}
private void clearDanmaku() {
mQueue.clear();
mDanmakuView.clearDanmakusOnScreen();
}
private void init() {
mContext = DanmakuContext.create();
// 设置最大显示行数
HashMap maxLinesPair = new HashMap<>();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 8); // 滚动弹幕最大显示5行
// 设置是否禁止重叠
HashMap 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 SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer
// .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 txt, boolean islive) {
BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
if (danmaku == null || mDanmakuView == null) {
return;
}
danmaku.text = txt;
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();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDanmakuView != null) {
// dont forget release!
mDanmakuView.release();
mDanmakuView = null;
}
if (mScheduledThreadPoolExecutor != null)
mScheduledThreadPoolExecutor.shutdown();
}
/**
* 从一系列颜色中随机选择一种颜色
*
* @return
*/
private int getRandomColor() {
int i = ((int) (Math.random() * 10)) % colors.length;
return colors[i];
}
}
代码运行结果:
附录:
1,《Android弹幕实现:基于B站弹幕开源系统(1)》链接地址:http://blog.csdn.net/zhangphil/article/details/68067100
2,《Java ConcurrentLinkedQueue队列线程安全操作》链接地址:http://blog.csdn.net/zhangphil/article/details/65936066