public abstract class BarrageAdapter
implements View.OnClickListener {
}
下面我们从数据的添加入口讲起:
/**
-
添加一组数据
-
@param dataList 一组数据
*/
public void addList(List dataList) {
if (dataList == null || dataList.size() == 0)
return;
int len = dataList.size();
mDataList.addAll(dataList);
mService.submit(new DelayRunnable(len));
}
mDataList是我们存放数据的 List,数据添加好之后,线程池会执行我们的任务 DelayRunnable, DelayRunnable是什么呢?看代码:
/**
- 延迟的Runnable
*/
public class DelayRunnable implements Runnable {
private int len;
DelayRunnable(int len) {
this.len = len;
}
@Override
public void run() {
if (repeat != -1 && repeat > 0) {
for (int j = 0; j < repeat; j++) {
sendMsg(len);
}
} else if (repeat == -1) {
while (!isDestroy.get()) {
sendMsg(len);
}
}
}
}
private void sendMsg(int len) {
for (int i = 0; i < len; i++) {
Message msg = new Message();
msg.what = MSG_CREATE_VIEW;
msg.obj = i;
mHandler.sendMessage(msg);
try {
Thread.sleep(interval * 20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可以看到, DelayRunnable实现了 Runnable接口, run()方法主要控制弹幕的循环次数, sendMsg(intlen)中不断发送消息给 mHandler,其中循环次数 repeat和发送消息的间隔 interval都是 IBarrageView提供的,而 mHandler就是生产过程中有的 BarrageAdapterHandler,主要负责子View的生成。
4.子 View 的生成
我们将 BarrageAdapterHandler设置成静态类。从数据变成 BarrageView子View的过程直接在下面的代码体现了出来:
public static class BarrageAdapterHandler extends Handler {
private WeakReference adapterReference;
BarrageAdapterHandler(Looper looper, BarrageAdapter adapter) {
super(looper);
adapterReference = new WeakReference<>(adapter);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_CREATE_VIEW: {
int pos = (int) msg.obj;
T data = (T) adapterReference.get().mDataList.get(pos);
if (data == null)
break;
if (adapterReference.get().barrageView == null)
throw new RuntimeException(“please set barrageView,barrageView can’t be null”);
// get from cache
View cacheView = adapterReference.get().barrageView.getCacheView(data.getType());
adapterReference.get().createItemView(data, cacheView);
}
}
}
}
先获取 msg.obj中的序号,从而从 mDataList中取出具体数据,接着从 IBarrageView中的 getCacheView(data.getType())获取缓存视图。
我们先抛开 BarrageAdapter,从 BarrageView中继续挖掘,在 BarrageView中,我们利用 SparseArray进行缓存弹幕子 View 的管理,根据不同的 DataSource中的 type,将缓存弹幕子 View 存进不同的 LinkedList中。
我们需要缓存弹幕子 View 的时候直接从 SparseArray里面取出一个子 View。
现在可以回到 BarrageAdapter了,我们来看 createItemView(data,cacheView)方法,这里就很像我们平时对 RecyclerView中 RecyclerAdapter的封装了:
/**
-
创建子视图的过程
-
@param cacheView 缓存视图
*/
public void createItemView(T data, View cacheView) {
// 1.获取子布局
// 2. 创建ViewHolder
// 3. 绑定ViewHolder
// 4. 返回视图
int layoutType = getItemLayout(data);
BarrageViewHolder holder = null;
if (cacheView != null) {
holder = (BarrageViewHolder) cacheView.getTag(R.id.barrage_view_holder);
}
if (null == holder) {
holder = createViewHolder(mContext, layoutType);
mTypeList.add(data.getType());
}
bindViewHolder(holder, data);
if (barrageView != null)
barrageView.addBarrageItem(holder.getItemView());
}
/**
-
创建ViewHolder
-
@param type 布局类型
-
@return ViewHolder
*/
private BarrageViewHolder createViewHolder(Context context, int type) {
View root = LayoutInflater.from(context).inflate(type, null);
BarrageViewHolder holder = onCreateViewHolder(root, type);
// 设置点击事件
root.setTag(R.id.barrage_view_holder, holder);
root.setOnClickListener(this);
return holder;
}
public abstract static class BarrageViewHolder {
public T mData;
private View itemView;
public BarrageViewHolder(View itemView) {
this.itemView = itemView;
}
public View getItemView() {
return itemView;
}
void bind(T data) {
mData = data;
onBind(data);
}
protected abstract void onBind(T data);
}
在子View的生成过程中:
a. 先获取子布局文件, getItemLayout(T t)是抽象方法,主要根据不同的数据类型确定不同的布局文件。
b. 接着判断缓存 View cacheView是否为空,不为空则利用 getTag(R.id.barrage_view_holder)方法获取缓存 View 中绑定的 BarrageViewHolder。
c. holder即 BarrageViewHolder为空的情况下就重新创建弹幕的子 View,这里我们可以从 createViewHolder(mContext,layoutType)中得处结论,子 View 就是在这里根据不同的布局文件创建的, Tag和弹幕的触摸事件的设置也是在这里设置的,这也就解决了上面的两个问题,如何设置多视图和触摸事件的检测。
d. bindViewHolder(holder,data);将 holder和具体的数据进行绑定。最终调用 BarrageViewHolder中的抽象 onBind(T data)方法,从而进行 UI 的设置。
e. 最后, IBarrageView将子弹幕子 View 添加进去。
5. BarrageView对子 View 的处理
子 View 添加来之后, BarrageView会对子 View 进行高度和宽度的测量,测量完之后进行最佳弹幕航道的选择和速度的设置,最后进行属性动画的创建,我们逐个分析。
宽度和高度的设置
@Override
public void addBarrageItem(final View view) {
// 获取高度和宽度
int w = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
view.measure(w, h);
final int itemWidth = view.getMeasuredWidth();
final int itemHeight = view.getMeasuredHeight();
if (singleLineHeight == -1) {
// 如果没有设置高度 启用添加的第一个Item作为行数
// 建议使用最小的Item的高度
singleLineHeight = itemHeight;
initBarrageListAndSpeedArray();
}
// 先省略后面代码
}
/**
- 初始化一个空的弹幕列表和速度列表
*/
private void initBarrageListAndSpeedArray() {
barrageDistance = DeviceUtils.dp2px(getContext(), 12);
barrageLines = height / (singleLineHeight + barrageDistance);
for (int i = 0; i < barrageLines; i++) {
barrageList.add(i, null);
}
speedArray = new int[barrageLines];
for (int i = 0; i < barrageLines; i++) {
speedArray[i] = 0;
}
}
在上面代码中,我们获取了子View的高度和宽度,如果是第一次添加子 View,同时用户也没有对弹幕的高度进行设置。
这个时候只能由 BarrageView自身进行 barrageList和 speedArray进行初始化, barrageList是 List,用来管理每个弹幕航道最新弹幕的子 View, speedArray是 int[],则用于管理最新弹幕子 View 的速度,他们可以用来干嘛,这里先卖个关子。
获取最佳弹幕航道
获取最佳航道的代码比较多,这里就不写了,首先会根据弹幕的布局(可以将弹幕放在顶部、中间、底部和全屏)进行行数的过滤,接着从 barrageList获取每一行的子 View 从而获取 getX(),最终得出哪一行剩余的空间大。
你可能会有疑问,当前航道没有子 View 呢?
这种情况就简单了,直接返回该航道啊。
获取速度
/**
-
获取速度
-
@param line 最佳弹道
-
@param itemWidth 子View的宽度
-
@return 速度
*/
private int getSpeed(int line, int itemWidth) {
if (model == MODEL_RANDOM) {
return speed - speedWaveValue + random.nextInt(2 * speedWaveValue);
} else {
int lastSpeed = speedArray[line];
View view = barrageList.get(line);
int curSpeed;
if (view == null) {
curSpeed = speed - speedWaveValue + random.nextInt(2 * speedWaveValue);
// 如果当前为空 随机生成一个滑动时间
return curSpeed;
}
int slideLength = (int) (width - view.getX());
if (view.getWidth() > slideLength) {
// 数据密集的时候跟上面的时间间隔相同
return lastSpeed;
}
// 得到上个View剩下的滑动时间
int lastLeavedSlidingTime = (int) ((view.getX() + view.getWidth() ) / (float) lastSpeed)+1;
int fastestSpeed = (width) / lastLeavedSlidingTime;
fastestSpeed = Math.min(fastestSpeed, speed + speedWaveValue);
if (fastestSpeed <= speed - speedWaveValue) {
curSpeed = speed - speedWaveValue;
} else
curSpeed = speed - speedWaveValue + random.nextInt(fastestSpeed - (speed - speedWaveValue));
return curSpeed;
}
}
speed和 speedWaveValue分别是速度初始值和速度波动值
[speed-speedWaveValue ,speed+speedWaveValue]
代表弹幕的速度区间。
这里 BarrageView会先判断当前弹幕的模式,如果是 MODEL_RANDOM模式,我们直接随机生成弹幕速度就好了,不过需要在速度区间中生成;如果是防碰撞模式,我们需要:
1. 通过我们上面提到的 barrageList和 speedArray分别获取之前该航道前一个子 View 和其速度。
2. 如果前子 View 为空,跟随机模式生成速度的规则一样。
3
. 如果前子 View 不为空,我们需要获取前子 View 已经滑动的距离,并且根据它的速度计算剩下滑动的时间,用剩下滑动时间下我们计算当前子 View 在不碰撞的前提下能够设置最快的速度,计算好之后再在用户设置的速度区间和不超过最快速度的前提下随机生成一个速度。
当然,这并不是绝对的,如果弹幕生成间隔设置不理想的情况下,较短的时间内会产生大量的子 View,肯定会发生碰撞的,这个时候我们就直接设置前一个子 View 的速度。
弹幕滑动
这个我们利用属性动画完成即可:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
v40F-1712424700173)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术
[外链图片转存中…(img-mtWHqoYr-1712424700173)]
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
[外链图片转存中…(img-btlLha71-1712424700173)]