尾声
评论里面有些同学有疑问关于如何学习material design控件,我的建议是去GitHub搜,有很多同行给的例子,这些栗子足够入门。
有朋友说要是动真格的话,需要NDK以及JVM等的知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。
至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。
一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。
初学者,一句话,多练。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
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 的速度。
弹幕滑动
这个我们利用属性动画完成即可:
@Override
public void addBarrageItem(final View view) {
// 省略前面代码
// 生成动画
final ValueAnimator valueAnimator = ValueAnimator.ofInt(width, -itemWidth);
最后,如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。
小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!
不论遇到什么困难,都不应该成为我们放弃的理由!
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!