从2015年初接触android到现在想想也有不短的一段时间了,这期间从毕业设计到跟朋友一起接外包,到进入公司从事android开发,手中大大小小也经历了不少android的项目,然而直到读了研,信心满满的出去找工作的时候才发现,短短半年在学校学习其他课程的时光,android的东西竟已忘却了大半,面对面试官的你项目中曾经遇到过哪些难题,讲讲你当时是怎么解决的?What?Android有很难的东西吗?百度Github上不都有吗<黑人问号脸>?天知道我们后面的面试是怎么进行下去的,,,好吧,这个暂且不提,毕竟今天的主题不是吐槽,而是要掀开本人博客生涯的第一篇,毕竟,有些东西只有记下来,才感觉有可能会是自己的<笑哭>…
OK ,烟鬼正传,今天主要是回顾下之前项目中用到的自定义view弹幕效果的实现。
首先功能分析
我们需要实现一个自定义的view,实现如下几点功能:
- 弹幕中的每一条弹幕从右进从左出,背景透明
- 弹幕中的每一条弹幕包括用户头像和文字,进行图文混排
- 弹幕中的每一条弹幕从左边完全消失后,过段时间按照其在弹幕列表中的顺序循环出现
看到右进左出,我们本能的可能会想到用到view的滑动,动态的更新弹幕的translationX,但是由于每条弹幕的速度不一致,因此需要创建不同线程来管理不同弹幕的滑动,但是这显然是不明智的,因为弹幕的数量是不封顶的,用线程来管理耗费代价太大,因此只能另寻他法,于是只能从搞清自定义view的流程来入手,由于view的绘制主要有measure、layout、draw三大流程,其中measure确定view的测量宽高,layout确定view的最终宽高和四个顶点的位置,而draw则将view绘制到屏幕上。可以看出measure、layout对我们帮助不大,由此考虑能否一次将所有弹幕信息绘制在界面上,然后更新弹幕的坐标信息,继续调用view的draw使之重绘,从而形成在用户看来的滑动效果,事实证明是可行的。
实现思路
由于每一个DanmuView 中包括很多条滚动的弹幕,因此把每条弹幕封装成一个DanmuItem,而DanmuView则为每个DanmuItem的容器和管理者,同时为了弹幕展示的更加美观,为每个DanmuView 纵向设置N条弹道,每条弹道设置最多m条弹幕,把从服务器端获取的弹幕消息依次加入消息队列,每隔一秒钟从队列中取出一条消息封装成弹幕信息,然后随机的选择一条弹道进行绘制在界面,然后通过随机生成的速度参数更改其下一次应该绘制在界面的坐标,进行重绘,直到此条弹幕消息已从屏幕左边滑出,把其从正在移动的弹幕队列中删除,重新添加进入消息队列,等待相应的时间重新被取出,以此实现弹幕的循环展示效果。
具体实现
此次的实现主要包括三个文件DanmuView,DanmuItem,IDanmuItem。其中IDanmuItem为接口,方便后面的修改和扩充,里面主要封装了DanmuItem的共有属性和方法。
public interface IDanmuItem {
//通过DanmuView传来的canvas参数绘制自身
void doDraw(Canvas canvas);
//支持对弹幕字体大小的设置
void setTextSize(int sizeInDip);
//支持对弹幕字体颜色的设置
void setTextColor(int colorResId);
//支持对弹幕的起始位置进行设置
void setStartPosition(int x, int y);
//设置弹幕的速度因子,速度大小为basespeed*factor
void setSpeedFactor(float factor);
//获取速度因子
float getSpeedFactor();
//判断自身坐标是否已经完全滑出屏幕
boolean isOut();
//判断自身会不会与正在滑动的同一弹道的目标item发生碰撞
boolean willHit(IDanmakuItem runningItem);
//释放掉context
void release();
//得到自身的宽度
int getWidth();
//得到自身的高度
int getHeight();
//得到自身现在的X坐标
int getCurrX();
//得到自身现在的Y坐标
int getCurrY();
}
DanmuItem则为IDanmuItem接口的具体实现,这里就不粘出全部代码了,只贴出关键代码。在DanmuItem中其实主要实现的就是图文混排以及文字换行在界面的绘制和提供自身会不会与正在滑动的同一弹道的目标item发生碰撞的判断,还有是否已滑出窗口的判断。下面依次粘出关键代码。
图文混排
图文混排的实现主要靠SpannableString ,SpannableString其实和String一样,都是一种字符串类型,同样TextView也可以直接设置SpannableString作为显示文本,不同的是SpannableString可以通过使用其方法setSpan方法实现字符串各种形式风格的显示,重要的是可以指定设置的区间,也就是为字符串指定下标区间内的子字符串设置格式。
setSpan(Object what, int start, int end, int flags)方法需要用户输入四个参数,what表示设置的格式是什么,可以是前景色、背景色也可以是可点击的文本等等,start表示需要设置格式的子字符串的起始下标,同理end表示终了下标,flags属性就有意思了,共有四种属性:
Spanned.SPAN_INCLUSIVE_EXCLUSIVE 从起始下标到终了下标,包括起始下标
Spanned.SPAN_INCLUSIVE_INCLUSIVE 从起始下标到终了下标,同时包括起始下标和终了下标
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 从起始下标到终了下标,但都不包括起始下标和终了下标
Spanned.SPAN_EXCLUSIVE_INCLUSIVE 从起始下标到终了下标,包括终了下标
SpannableString spannableString = new SpannableString(content);
if (bitmap != null) {
//压缩bitmap
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, Dip2PxUtils.dip2px(context, 30),