轻松又酷炫地实现弹幕效果——手把手教学

作者姓名: 贾帅

CSDN: http://blog.csdn.net/jiashuai94

github: https://github.com/shuaijia

前言

更多代码,请查看我的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、实体类

实体类当然不能少了:

其中的type是model实体类的类型,因为弹幕的itemView中会有多种类型,对应不同type的实体类。

使用时可以自己定义实体类,继承自DanmuModel ,也可以不继承,只要能区分不同类型就可以:因为自己稍后的adapter中没有像ListView的Adapter一样定义了获取item类型的方法,所以就在getView方法中依据type选择不同的itemView即可

2、BaseAdapter

首先Adapter定义为抽象类,且设置泛型M,M就是对应的实体类。

在Adapter中定义三个抽象方法:

  1. getViewTypeArray :获取itemView的类型type组成的数组
  2. getSingleLineHeight :获取单行itemView的高度
  3. getView :获取itemView,功能类似于ListView的Adapter中getView方法

这样适配器抽象类就定义好了嘛?不是的!

在显示弹幕的时候会,会创建大量的View对象,如果不做处理,很容易造成内存溢出,所以我们要进行缓存优化

A、首先创建了map集合

以view的类型为key,对应的view存入栈中,以栈为value。

B、构造中

获取itemView类型数组,循环创建对应type的栈。

C、itemView加入缓存

D、将itemView移出缓存

E、减小缓存大小

F、获取缓存大小

ok,到这里BaseAdapter就封装完成了

3、DanmuView

继承自ViewGroup,重写其三个构造方法是必然的。不再累赘

A、变量、以及get/set方法

首先要有这样一个思路,在适配器中抽取出方法,返回itemView的高度,在弹幕View中根据弹幕绘制区域高度,除以itemView的高度,算出合理的弹幕行数(这里大家也理解了为什么在写适配器的时候要定义getSingleLineHeight()方法了)。

B、再次封装实体类

这里只是简单得将传进来的实体类DanmuModel与计算出的对应的最佳行数进行封装。

C、设置Adapter

D、计算最佳位置

关键的来了,先上代码

不知是否有注意到,在定义显示位置的常亮的时候,只用了1,2,4,7,因为它们转化为二进制数为001,010,100,111,这里用了一个巧妙的思路,三位数代表屏幕三个位置,0表示不显示弹幕,1表示显示弹幕(有没有豁然开朗)

大家可以参照代码来看,计算最佳位置的思路是这样的:

  1. 将设置的位置转为二进制数,判断显示位置
  2. 将所有的行分为三份,前两份行数相同,将第一份的行数四舍五入,将所有要显示弹幕的行数放入一集合中
  3. 由上至下循环判断是否有空行,有空行则直接返回,此行就是这个itemView的最佳位置
  4. 没有空行的话,由下至上寻找最大空间返回,就是该itemView的最佳位置

E、根据类型设置View

这里就不多说了,将itemView的model与最佳位置对应起来并设置位置

然后将spanList(itemView集合)对应view设置进去。

一定要注意:super.addView(child); child.measure(0, 0); 这两句话不能少!

F、添加弹幕

此方法则是暴露外部的设置弹幕view的方法,这里注意一下,itemView有缓存就复用,没缓存就不复用,就ok了。

G、子线程计算时间,发送消息,handler处理view平移

这里注意: Adapter缓存过大要及时清理; 每隔16毫秒让itemView位置刷新一次,这样视觉效果好一些; 在setAdapter中开启线程 new Thread(new MyRunnable()).start();

不再累赘,如果阅读完整代码,可以到我github查看源码和issue我^_^https://github.com/shuaijia/JsPlayer

使用举例

1

实体类

2

适配器

有木有很像ListView的Adapter! 相信大家一看就能明白,就不再多说。

3

配置信息

4

创建实体类,并设置给DanmuView

原文发布于微信公众号 - Android机动车(JsAndroidClub)

原文发表时间:2017-10-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python中文社区

Python中被忽略的else

else, 我们再熟悉不过了。对于一个python程序员来说,else往往都是配合if来使用的,像这样:

1172
来自专栏tkokof 的技术,小趣及杂念

Coroutine,你究竟干了什么?(小续)

前篇中讲了一些自己关于Coroutine的理解,后来陆陆续续的又想到了一些,在此简单记录一下,内容不多,故作“小”续吧 :)

842
来自专栏Phoenix的Android之旅

设计模式之工厂模式

工厂模式作为设计模式的一种在开发中被普遍使用, 其实应该可以说是最经常使用的一种的了。 它的设计思想也是面向接口,如果细分下来,可以分成两种工厂模式 · 工厂方...

741
来自专栏菩提树下的杨过

rpc框架之gRPC 学习 - hello world

grpc是google在github于2015年开源的一款RPC框架,虽然protobuf很早google就开源了,但是google一直没推出正式的开源框架,导...

7587
来自专栏肖蕾的博客

图文并茂解释Kotlin == 和 === 之间的差异

1003
来自专栏NetCore

[原创]Fluent NHibernate之旅(三)-- 继承

经过了“开篇”和“简单映射”两篇文章,相信大家对Fluent NHibernate 有了一定的了解了,FluentNHibernate实际就是对 NHibern...

2008
来自专栏用户2442861的专栏

*Android面试实战总结2

 接上一篇 往下写 http://blog.csdn.net/u011733020/article/details/45998861  ,   非常感谢在上...

1053
来自专栏偏前端工程师的驿站

JS魔法堂:不完全国际化&本地化手册 之 实战篇

前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求——国际化&本地化。熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"...

26210
来自专栏申龙斌的程序人生

零基础学编程002:Hello World

昨天介绍了codecademy在线学编程的网站,不知道大家动手试验了没有?是不是太简单,一下子就完成了许多练习? 第一课的内容只有一条输出语句,点击保存并提交...

2897
来自专栏Java呓语

生成器模式(分离部件构造)

生成器模式的主要功能是构建复杂的产品,而且是细化的,分步骤的构建产品,也就是生成器模式重在解决一步一步构造复杂对象的问题。如果光是这么认识生成器模式的功能是不够...

912

扫码关注云+社区

领取腾讯云代金券