**
**
本文主要介绍基于哔哩哔哩弹幕库实现的图文弹幕,包含头像,名称和弹幕内容,就像下图酱紫,其实也是起到抛砖引玉的作用,当你理解我的实现方式后,如果想实现单行文字啦,或者更加复杂的排版啦,都是改变一下绘制方式而已。
废话不多说,先上代码,看着代码来讲解:
public class DanmuActivity extends Activity implements View.OnClickListener {
private IDanmakuView mDanmakuView;
private Button mBtnSendDanmaku;
private EditText editText;
private DanmuControl danmuControl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_danmuku);
mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku);
editText = (EditText) findViewById(R.id.editText);
mBtnSendDanmaku = (Button) findViewById(R.id.btn_send);
mBtnSendDanmaku.setOnClickListener(this);
danmuControl = new DanmuControl(this, mDanmakuView);
}
@Override
public void onClick(View v) {
String avator = "http://g.hiphotos.baidu.com/image/h%3D200/sign=9b2f9371992397ddc9799f046983b216/dc54564e9258d1094dc90324d958ccbf6c814d7a.jpg";
String name = "张三";
String content = editText.getText().toString().trim();
if (content != null && content.length() > 0) {
danmuControl.addDanmu(avator, name, content);
} else {
Toast.makeText(this, "发送内容为空", Toast.LENGTH_LONG).show();
}
}
}
"http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fffeabab">
"@+id/sv_danmaku"
android:layout_width="match_parent"
android:layout_height="match_parent" />
"match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="#8acc0000"
android:gravity="center_vertical"
android:padding="5dp">
"@+id/editText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
以上部分是主页面以及主页面的布局。
public class DanmuControl {
private Context mContext;
private IDanmakuView mDanmakuView;
private DanmakuContext mDanmakuContext;
public DanmuControl(Context context, IDanmakuView danmakuView) {
this.mContext = context;
this.mDanmakuView = danmakuView;
initDanmuConfig();
}
/**
* 初始化配置
*/
private void initDanmuConfig() {
// 设置最大显示行数
HashMap maxLinesPair = new HashMap();
maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 2); // 滚动弹幕最大显示2行
// 设置是否禁止重叠
HashMap overlappingEnablePair = new HashMap();
overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true);
overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true);
mDanmakuContext = DanmakuContext.create();
mDanmakuContext
.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_NONE)
.setDuplicateMergingEnabled(false)
.setScrollSpeedFactor(3f)//越大速度越慢
.setScaleTextSize(1.2f)
.setCacheStuffer(new MyCacheStuffer(mContext), mCacheStufferAdapter)
.setMaximumLines(maxLinesPair)
.preventOverlapping(overlappingEnablePair);
if (mDanmakuView != null) {
mDanmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void prepared() {
mDanmakuView.start();
}
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
@Override
public void drawingFinished() {
}
});
}
mDanmakuView.prepare(new BaseDanmakuParser() {
@Override
protected Danmakus parse() {
return new Danmakus();
}
}, mDanmakuContext);
mDanmakuView.enableDanmakuDrawingCache(true);
}
private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() {
@Override
public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) {
}
@Override
public void releaseResource(BaseDanmaku danmaku) {
// tag包含bitmap,一定要清空
danmaku.tag = null;
}
};
public void addDanmu(final String avatorUrl, final String name, final String content) {
final BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
new Thread() {
@Override
public void run() {
InputStream inputStream = null;
try {
// 从网络获取图片并且保存到一个bitmap里
URLConnection urlConnection = new URL(avatorUrl).openConnection();
inputStream = urlConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
bitmap = makeRoundCorner(bitmap);
// 组装需要传递给danmaku的数据
Map map = new HashMap();
map.put("name", name);
map.put("content", content);
map.put("bitmap", bitmap);
danmaku.tag = map;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(inputStream);
}
danmaku.text = "";
danmaku.padding = 0;
danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕
danmaku.isLive = true;
danmaku.time = mDanmakuView.getCurrentTime() + 1000;
danmaku.textSize = 0;
mDanmakuView.addDanmaku(danmaku);
}
}.start();
}
/**
* 将图片变成圆形
*
* @param bitmap
* @return
*/
private static Bitmap makeRoundCorner(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int left = 0, top = 0, right = width, bottom = height;
float roundPx = height / 2;
if (width > height) {
left = (width - height) / 2;
top = 0;
right = left + height;
bottom = height;
} else if (height > width) {
left = 0;
top = (height - width) / 2;
right = width;
bottom = top + width;
roundPx = width / 2;
}
Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
int color = 0xff424242;
Paint paint = new Paint();
Rect rect = new Rect(left, top, right, bottom);
RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
}
public class MyCacheStuffer extends BaseCacheStuffer {
private int AVATAR_DIAMETER; //头像直径
private int AVATAR_PADDING; // 头像边框宽度
private int TEXT_LEFT_PADDING; // 文字和头像间距
private int TEXT_RIGHT_PADDING; // 文字和右边线距离
private int TEXT_SIZE; // 文字大小
private int NICK_COLOR = 0xffff1874;//昵称 红色
private int TEXT_COLOR = 0xffeeeeee; //文字内容 白色
private int TEXT_BG_COLOR = 0x66000000; // 文字灰色背景色值
private int TEXT_BG_RADIUS; // 文字灰色背景圆角
public MyCacheStuffer(Context context) {
// 初始化固定参数,这些参数可以根据自己需求自行设定
AVATAR_DIAMETER = Util.DensityUtil.dip2px(context, 33);
AVATAR_PADDING = Util.DensityUtil.dip2px(context, 1);
TEXT_LEFT_PADDING = Util.DensityUtil.dip2px(context, 2);
TEXT_RIGHT_PADDING = Util.DensityUtil.dip2px(context, 8);
TEXT_SIZE = Util.DensityUtil.dip2px(context, 13);
TEXT_BG_RADIUS = Util.DensityUtil.dip2px(context, 8);
}
@Override
public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) {
// 初始化数据
Map map = (Map) danmaku.tag;
String name = (String) map.get("name");
String content = (String) map.get("content");
// 设置画笔
paint.setTextSize(TEXT_SIZE);
// 计算名字和内容的长度,取最大值
float nameWidth = paint.measureText(name);
float contentWidth = paint.measureText(content);
float maxWidth = Math.max(nameWidth, contentWidth);
// 设置弹幕区域的宽高
danmaku.paintWidth = maxWidth + AVATAR_DIAMETER + AVATAR_PADDING * 2 + TEXT_LEFT_PADDING + TEXT_RIGHT_PADDING; // 设置弹幕区域的宽度
danmaku.paintHeight = AVATAR_DIAMETER + AVATAR_PADDING * 2; // 设置弹幕区域的高度
}
@Override
public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) {
}
@Override
public void drawText(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, TextPaint paint, boolean fromWorkerThread) {
}
@Override
public void clearCaches() {
}
@Override
public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) {
// 初始化数据
Map map = (Map) danmaku.tag;
String name = (String) map.get("name");
String content = (String) map.get("content");
Bitmap bitmap = (Bitmap) map.get("bitmap");
// 设置画笔
Paint paint = new Paint();
paint.setTextSize(TEXT_SIZE);
// 绘制文字灰色背景
Rect rect = new Rect();
paint.getTextBounds(content, 0, content.length(), rect);
paint.setColor(TEXT_BG_COLOR);
paint.setAntiAlias(true);
float bgLeft = left + AVATAR_DIAMETER / 2 + AVATAR_PADDING;
float bgTop = top + AVATAR_DIAMETER / 2 + AVATAR_PADDING;
float bgRight = left + AVATAR_DIAMETER + AVATAR_PADDING * 2 + TEXT_LEFT_PADDING + rect.width() + TEXT_RIGHT_PADDING;
float bgBottom = top + AVATAR_DIAMETER + AVATAR_PADDING;
canvas.drawRoundRect(new RectF(bgLeft, bgTop, bgRight, bgBottom), TEXT_BG_RADIUS, TEXT_BG_RADIUS, paint);
// 绘制头像背景
paint.setColor(Color.WHITE);
float centerX = left + AVATAR_DIAMETER / 2 + AVATAR_PADDING;
float centerY = left + AVATAR_DIAMETER / 2 + AVATAR_PADDING;
float radius = AVATAR_DIAMETER / 2 + AVATAR_PADDING; // 半径
canvas.drawCircle(centerX, centerY, radius, paint);
// 绘制头像
float avatorLeft = left + AVATAR_PADDING;
float avatorTop = top + AVATAR_PADDING;
float avatorRight = left + AVATAR_PADDING + AVATAR_DIAMETER;
float avatorBottom = top + AVATAR_PADDING + AVATAR_DIAMETER;
canvas.drawBitmap(bitmap, null, new RectF(avatorLeft, avatorTop, avatorRight, avatorBottom), paint);
// 绘制名字
paint.setColor(NICK_COLOR);
float nameLeft = left + AVATAR_DIAMETER + AVATAR_PADDING * 2 + TEXT_LEFT_PADDING;
float nameBottom = top + rect.height() + AVATAR_PADDING + (AVATAR_DIAMETER / 2 - rect.height()) / 2;
canvas.drawText(name, nameLeft, nameBottom, paint);
// 绘制弹幕内容
paint.setColor(TEXT_COLOR);
float contentLeft = nameLeft;
float contentBottom = top + AVATAR_PADDING + AVATAR_DIAMETER / 2 + rect.height() + (AVATAR_DIAMETER / 2 - rect.height()) / 2;
canvas.drawText(content, contentLeft, contentBottom, paint);
}
}
以上就是图文弹幕实现的核心内容,先来看看DanmuControl这类:
这个类主要做弹幕库的初始化设置,很多内容哔哩哔哩弹幕库的demo里都有介绍,我就不多说了,我们的重点是图文弹幕。
下面介绍MyCacheStuffer 这个类,这个是实现图文弹幕最核心的地方:
这个类继承自BaseCacheStuffer ,哔哩哔哩弹幕库提供的几个方法让我们用来进行内容,背景,边界的绘制。
源码下载
这是我的第一篇原创技术文章,做技术时间久了,就有很多东西想拿出来分享,希望这篇文章能帮助到一些用到弹幕的同学,文笔一般,如有表述不清晰的地方,请指正。