package com.netease.nim.uikit.common.ui.barrage;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import com.netease.nim.uikit.common.util.sys.ScreenUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
/**
* 弹幕控件
*
* Created by huangjun on 2016/5/8.
*/
public class BarrageSurfaceView extends SurfaceViewTemplate {
private static final String TAG = "BarrageSurfaceView";
private static final boolean OUTPUT_LOG = true;
private static final int DEFAULT_RANDOM_COLOR_NUM = 30;
private static final int TIME_INTERVAL = 30;
private static final int MESSAGE_FREE_LINE = 0x01;
private static final int MESSAGE_END = 0x02;
private static final String MESSAGE_DATA_LINE = "LINE";
private Random random;
// 配置管理
private BarrageConfig config;
// 轨道管理
private Set<Integer> linesUnavailable = new HashSet<>();
private int lineCount;
private int lineHeight;
// 字幕管理
private Queue<String> textCache = new LinkedList<>();
// 执行者管理(多线程访问)
private final List<BarrageTextTask> tasks = new LinkedList<>();
public BarrageSurfaceView(Context context) {
super(context);
}
public BarrageSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void init(final BarrageConfig config) {
this.random = new Random();
int totalLineHeight = getBottom() - getTop() - getPaddingTop() - getPaddingBottom();
this.lineHeight = ScreenUtil.sp2px(config.getMaxTextSizeSp());
this.lineCount = totalLineHeight / lineHeight;
// random colors
if (config.getColors() == null || config.getColors().isEmpty()) {
List<Integer> colors = new ArrayList<>(DEFAULT_RANDOM_COLOR_NUM);
for (int i = 0; i < DEFAULT_RANDOM_COLOR_NUM; i++) {
colors.add(Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
}
config.setColors(colors);
}
this.config = config;
log("barrage init, lineHeight=" + lineHeight + ", lineCount=" + lineCount);
}
public void addTextBarrage(String text) {
if (TextUtils.isEmpty(text)) {
return;
}
textCache.add(text);
checkAndRunTextBarrage();
}
private void checkAndRunTextBarrage() {
if (textCache.isEmpty()) {
return;
}
int availableLine = getAvailableLine();
if (availableLine < 0) {
return; // pend
}
BarrageTextTask task = buildBarrageTextTask(textCache.poll(), availableLine);
synchronized (tasks) {
tasks.add(task);
}
notifyHasTask(); // 通知有绘制任务
}
private int getAvailableLine() {
int line = -1;
for (int i = 0; i < lineCount; i++) {
if (!linesUnavailable.contains(i)) {
line = i;
break;
}
}
if (line >= 0 && line < lineCount) {
linesUnavailable.add(line); // 占用
}
return line;
}
private BarrageTextTask buildBarrageTextTask(String text, int line) {
if (TextUtils.isEmpty(text)) {
return null;
}
// text size, length
int size = config.getMinTextSizeSp() + random.nextInt(config.getMaxTextSizeSp() - config.getMinTextSizeSp() + 1);
size = ScreenUtil.sp2px(size);
// text color
int color;
if (config.getColors() != null && !config.getColors().isEmpty()) {
color = config.getColors().get(random.nextInt(config.getColors().size()));
} else {
color = Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256));
}
// duration
int duration = config.getDuration() + random.nextInt() % 500;
// start position
float x = getWidth();
float y = size + line * lineHeight;
// speed
float deltaX = (1.0f * getWidth() / duration) * TIME_INTERVAL;
// log
StringBuilder log = new StringBuilder();
log.append("build text barrage task")
.append(", line=").append(line)
.append(", text=").append(text)
.append(", speed=").append(deltaX);
log(log.toString());
return new BarrageTextTask(text, line, color, size, duration, x, y, deltaX);
}
private Handler mHandler = new Handler(getContext().getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_FREE_LINE:
onLineAvailable(msg.getData().getInt(MESSAGE_DATA_LINE));
break;
case MESSAGE_END:
onTextBarrageDone(msg.getData().getInt(MESSAGE_DATA_LINE));
break;
default:
super.handleMessage(msg);
}
}
};
private void onLineAvailable(final int line) {
log("free line, line=" + line);
linesUnavailable.remove(line);
checkAndRunTextBarrage();
}
private void onTextBarrageDone(final int line) {
log("text barrage completed, line=" + line);
checkAndRunTextBarrage();
}
/**
* ******************************** 在线程中执行绘制 ************************************
*/
@Override
public void onDrawView(Canvas canvas) {
synchronized (tasks) {
if (tasks.size() <= 0) {
return;
}
Iterator<BarrageTextTask> iterator = tasks.iterator();
BarrageTextTask task;
while (iterator.hasNext()) {
task = iterator.next();
// draw
task.updatePosition();
canvas.drawText(task.getText(), task.getX(), task.getY(), task.getPaint());
// check end
if (task.isEnd()) {
iterator.remove(); // reach the end,remove
Message message = new Message();
message.what = MESSAGE_END;
Bundle data = new Bundle();
data.putInt(MESSAGE_DATA_LINE, task.getLine());
message.setData(data);
mHandler.sendMessage(message);
continue;
}
// check free line
if (task.canFreeLine()) {
Message message = new Message();
message.what = MESSAGE_FREE_LINE;
Bundle data = new Bundle();
data.putInt(MESSAGE_DATA_LINE, task.getLine());
message.setData(data);
mHandler.sendMessage(message);
}
}
}
}
@Override
public int getRunTimeInterval() {
return TIME_INTERVAL;
}
@Override
protected boolean hasTask() {
boolean hasTask;
synchronized (tasks) {
hasTask = tasks.size() > 0;
}
return hasTask;
}
private void log(String message) {
if (OUTPUT_LOG) {
Log.i(TAG, message);
}
}
}