前言
更多代码,请查看我的github:https://github.com/shuaijia/JsPlayer ,喜欢的话就给个star!^_^
也可以关注我的公众号,搜索 安卓干货营
现在越来越多的视频网站或者客户端支持弹幕功能,弹幕功能似乎也成了很多人的爱好,发弹幕,看弹幕成了大家吐槽、搞笑、发表看法的一种方式。
而国内弹幕的鼻祖应该就算A站和B站了。
弹幕(barrage),中文流行词语,原意指用大量或少量火炮提供密集炮击。而弹幕,顾名思义是指子弹多而形成的幕布,大量吐槽评论从屏幕飘过时效果看上去像是飞行射击游戏里的弹幕。
最近一直在写视频播放器,那弹幕怎么能少得了呢!所以把自己开发弹幕功能的思路写出来与大家分享。
依旧还是先上效果图:
大体思路
我们的目标是将各式各样的itemView展示到播放器上方,并且使之滚动起来,itemView支持自定义,这样看起来和ListView的功能很相像,但与之不一样的是,弹幕是多行多列,需要计算每个itemView的位置,且一直在滚动。
所以,我采用适配器模式,仿ListView的Adapter来实现弹幕功能。
想到这里,很多人就会觉得这不典型的横向瀑布流嘛,用RecyclerView或者flexbox很轻松就实现了。
但我想自己从设计模式、实现原理来考虑、设计,从而也可以更深刻地理解适配器模式和ListView的原理,如果您想使用RecyclerView来实现,可以自己试试。
关键:
- 使用适配器模式将各式各样的itemView进行适配、处理、展示
- 使用hadler定时发送消息使itemView滚动
- itemView最佳位置的计算
- 滚动区域的设置
接下来就一起来实现:
1、实体类
实体类当然不能少了:
/**
* Description: 弹幕实体类
* Created by jia on 2017/9/25.
* 人之所以能,是相信能
*/
public class DanmuModel {
private int type;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
其中的type是model实体类的类型,因为弹幕的itemView中会有多种类型,对应不同type的实体类。
使用时可以自己定义实体类,继承自DanmuModel ,也可以不继承,只要能区分不同类型就可以:因为自己稍后的adapter中没有像ListView的Adapter一样定义了获取item类型的方法,所以就在getView方法中依据type选择不同的itemView即可。
2、BaseAdapter
首先Adapter定义为抽象类,且设置泛型M,M就是对应的实体类。
在Adapter中定义三个抽象方法:
- getViewTypeArray :获取itemView的类型type组成的数组
- getSingleLineHeight :获取单行itemView的高度
- getView :获取itemView,功能类似于ListView的Adapter中getView方法
public abstract class DanmuAdapter<M> {
/**
* 获取类型数组
*
* @return
*/
public abstract int[] getViewTypeArray();
/**
* 获取单行弹幕 高度
*
* @return
*/
public abstract int getSingleLineHeight();
/**
* 获取itemView
*
* @param entry
* @param convertView
* @return
*/
public abstract View getView(M entry, View convertView);
}
这样适配器抽象类就定义好了嘛?不是的!
在显示弹幕的时候会,会创建大量的View对象,如果不做处理,很容易造成内存溢出,所以我们要进行缓存优化:
A、首先创建了map集合
// 使用HashMap,以类型和对应view的栈为key-value存储,实现缓存
private HashMap<Integer, Stack<View>> cacheViews;
以view的类型为key,对应的view存入栈中,以栈为value。
B、构造中
public DanmuAdapter() {
cacheViews = new HashMap<>();
typeArray = getViewTypeArray();
for (int i = 0; i < typeArray.length; i++) {
Stack<View> stack = new Stack<>();
cacheViews.put(typeArray[i], stack);
}
}
获取itemView类型数组,循环创建对应type的栈。
C、itemView加入缓存
/**
* 将弹幕itemView加入缓存(压栈)
*
* @param type
* @param view
*/
synchronized public void addViewToCache(int type, View view) {
if (cacheViews.containsKe