基于Android淡入淡出弹幕实现
Posted clumsypanda
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Android淡入淡出弹幕实现相关的知识,希望对你有一定的参考价值。
最近遇到需求,需要在某张用户发表的图片中展示评论,方式是以淡入淡出的弹幕形式。需求为淡入事件t1->淡出t2,所有弹幕依次开始播放,中间一定的时间间隔deltaT。仔细考虑之后,想到以下实现方式,现总结下来并进行实现、分析以及比较。
不妨认定弹幕的画布为BarrageView;每条弹幕的绘制为BarrageItemView。
- 属性动画
从只有一条弹幕开始思考,我们需要设计一个属性动画,负责淡入:
在该动画的完成回调函数onAnimationEnd中启动淡出动画,在淡出动画的完成回调函数中,对该BarrageItemView进行remove操作,这样就完成了一条弹幕的显示过程。
多条弹幕展示时也是同样的逻辑。
使用这种方法,弹幕的依次播放在实现上是一个难点:我们需要另开一个通知线程(计时器),在每隔deltaT时间,对BarrageView(弹幕画布)进行通知,告诉它你可以播放一条新的弹幕了。
于是在每个BarrageView中我们需要建一个handle来对通知消息进行处理,当BarrrageView是作为ListView或者RecyclerView的一个Item时,通过用户的活动,可能会展示大量的BarrageView,这样会构建出大量的Handler,对系统性能也是一个挑战。不过如果处于RecyclerView的Item中,在RecyclerView对ViewHolder进行重用时,也可以在Handler实现中进行判断和重用,以最大程度减少资源浪费。这样的实现方法会出现一些的问题:太多的BarrageItemView和属性动画的创建。这样的做法不仅会影响应用的性能,而且每条弹幕和属性动画的管理都会对代码实现提出挑战。
另一个需要注意的点是,弹幕动画往往不是稳定的从开始播放到结束,用户可以进行暂停、关闭、打开弹幕等操作。这一系列管理功能决定了我们在使用属性动画时必须记录每个当前正在播放的BarrageItemView的引用以及ObjectAnimator的引用。用户在关闭弹幕时,系统需要cancel动画,此时需要调用该动画对应的BarrageItemView的remove操作,因为处在主线程中,而对ObjectAnimator和BarrageItemView的添加是处在Handler中的,那么这样的处理涉及到一个多线程访问的问题,很有可能会造成对同一个ArrayList的添加删除冲突,导致crash,这里的管理实现尤其需要小心。
动画的管理不妨考虑使用属性动画集合AnimatorSet,虽然在动画的管理会方便点,但是上述的问题仍然存在,也是不可避免的。如果遇到弹幕淡入>停留->弹幕淡出逻辑,动画数目更为之多,AnimatorSet有一定的优势。
具体实现的过程中,我发现最大的问题是ObjectAnimator和BarrageItemView的管理,当进行弹幕的关闭(Clear)时,如果有一个弹幕的删除出错或者一个动画的cancel出错,那么屏幕中的逻辑就会混乱。如果评论有N条,在弹幕创建时候若每次开始播放时候根据评论创建一个新的BarrageItemView,那么如果上述逻辑出错,BarrageView中经常会出现大量重复弹幕。所以必须对所有评论先创建所有的弹幕,收到一条通知消息,播放一条,这样的逻辑是比较清楚的。
- Canvas绘制
核心思路是设置paint的alpha,对bitmap在ondraw方法中进行绘制,具体方案及流程如下:
1、定义一个可以被XML使用的弹幕画布:BarrageView
2、基本变量初始化
使用一个自定义数据结构来存储弹幕的成员变量:
在BarrageView的设置弹幕列表的函数中,对弹幕数据结构的curAlpha成员变量进行初始化,因为需要解决弹幕出现有时间间隔deltaT的问题,所以通过计算,将curAlpha值列表初始化为-*i*ALPHA_START_STEP,当curAlpha值为负数时,不对其进行绘制,在下个绘制时隙中该值会得到FADE_STEP的增加,当其大于0时开始使用paint根据画家算法进行覆盖绘制。这里ALPHA_START_STEP表示的就是各个弹幕之间的时隙deltaT。
3、创建每个弹幕的BarrageItemView,并将其转化成Bitmap。
4、在ondraw方法中实现弹幕Bitmap的绘制逻辑:
对当前每个弹幕进行判断,是处于淡入状态还是淡出状态,若是前者,将alpha值增加,用paint进行绘制,后者则为减少。
为了实现淡入->停留->淡出逻辑,可以让alpha增加的最大值大于255,但是在真正绘制时进行判断,若当前alpha大于255,则选用255进行绘制,即实现了停留效果。
淡入实现:
淡出实现逻辑类似,不过在淡出之后需要将地址位置重新更新,实现弹幕在下次播放时选择另一个随机位置。
在所有绘制进行完毕,则调用:
在下次时隙中插入界面重绘的标志,实现了弹幕的持续展示。
值得注意的是,FADE_STEP是计算出来的alpha值变化速率,在对其进行计算时,要考虑到android系统每隔16ms会发出VSYNC信号通知系统进行渲染绘制。所以要注意两点:所有在ondraw中需要执行的操作必须在16ms完成,否则会造成在下一次绘制时还未计算完,造成丢帧;不用将FADE_STEP值设置过小,因为对下一次绘制我们将手动调用触发算法,所以发出过多的重绘信号也会根据系统的VSYNC信号进行重算覆盖,因此在此需要进行权衡。
5、实现响应事件:
因为弹幕的表现形式不再是View,而是Bitmap,所以整个弹幕画布的点击事件需要使用GestureDetectorCompat来控制:
这个方法经过测试发现是比较高效的,但是有一个很大的问题:如果弹幕展示只有文字,那么该方法效率和性能都很好,也能完成需求。但是如果弹幕包涵图片(如头像),那么图片的请求将会是一个异步过程。很难保证在Bitmap生成时图片已经加载完毕。
- Ondraw时设置每条弹幕的alpha
该方案实现逻辑和方案二很像,不再赘述详细逻辑,不同的是每个BarrageItemView数据结构中不再存储Bitmap,而是直接存储了View或者Layout。这样可以完美地解决上述的图片加载问题。在每次onDraw方法调用时候不再更新paint的alpha,而是获取BarrageItemView新的alpha值,直接对其进行设置,如淡入控制:
简单测试了下性能,使用手机机型:HUAWEI MT7-TL00;Android 4.4.2,level 19,ROM:HuaWei/EMUI/EmotionUI_3.0
随机展示一些弹幕画布,不均匀地含有一些弹幕,测试时包括动画的打开、关闭、暂停、继续等,CPU占用率监控结果如下:
平稳播放时:
方案一:7%-10%
方案二:3%-7%
方案三:3%-8%
上图为方案三的监控截图,分别执行了:打开、暂停、继续播放、关闭、打开的操作。可见在不播放弹幕的状态,Demo对CPU的占用率是极低的。使用属性动画对CPU的占用率最高,而且需要大量的动画创建、删除和执行,例如在管理时需要对属性动画和BarrrageItemView的引用也需要及时更新,都带来了很大的开销。
最后附上一个效果图:
以上是关于基于Android淡入淡出弹幕实现的主要内容,如果未能解决你的问题,请参考以下文章
Android属性动画小练习(简单实现旋转平移淡入淡出缩放动画效果)
请问 在Android编辑mp3时,如何设置mp3的淡入淡出效果?