Android Flutter实现弹幕效果
作者:JulyYu 发布时间:2022-07-17 16:15:17
前言
需求要点如下:
弹幕行数为3行,每条弹幕相互依靠但不存在重叠
每条弹幕可交互点击跳转
滚动速度恒定 触摸不可暂停播放
弹幕数据固定一百条且支持轮询播放
弹幕排序规则如下:
1 4 7
2 5 8
3 6 9
通用弹幕实现方案
Flutter Dev Package
已有开源弹幕实现组件,这里举例barrage_page
的实现方式(大多数实现底层逻辑基本一样)。
基本架构采用Stack然后向布局中提交弹幕布局,添加时设置好弹幕偏移量来设置弹幕位置。
Stack(fit: StackFit.expand, children: <Widget>[
widget.child,
_controller.isEnabled
? Stack(
fit: StackFit.loose,
children: <Widget>[]
..addAll(_widgets.values ?? const SizedBox()))
: const SizedBox(),
]);
});
弹幕效果代码
但因为每条弹幕可能会出现重叠情况无法合理定位每条弹幕的位置因此放弃该方案。
PS:widget只有在build到布局后才能获取到它基础信息(相对位置信息,宽高等)就无法计算出所有弹幕的位置信息。
ListView弹幕方案实现
最先想到使用瀑布流flutter_staggered_grid_view
实现弹幕布局但由于组件暂时不支持横向布局就放弃了。
基本框架
采用三个ListView实现每一行弹幕效果。虽然不太推荐以这种形式实现但从快速实现效果来说是比较简单便捷兜底方案。(可以实现但不推荐)
Container(
height: 200,
child: Column(
children: [
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: scrollController1,
itemBuilder: (context, index) {
return Common.getWidget(index,
height: 30, width: random.nextInt(100).toDouble());
},
),
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: scrollController2,
itemBuilder: (context, index) {
return Common.getWidget(index,
height: 30, width: random.nextInt(100).toDouble());
},
)),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
controller: scrollController3,
itemBuilder: (context, index) {
return Common.getWidget(index,
height: 30, width: random.nextInt(100).toDouble());
},
))
],
),
)
轮播滚动
添加定时器periodic
定时每秒钟执行一次scrollController
的animateTo
方法移动偏移量并且偏移量不断累加。
其次ListView
支持无限滑动只要ListView.builder
不设置itemCount
就能实现。
Timer _timer;
scroll = () {
offset += 100;
scrollController1.animateTo(offset,
duration: Duration(seconds: 1), curve: Curves.linear);
scrollController2.animateTo(offset,
duration: Duration(seconds: 1), curve: Curves.linear);
scrollController3.animateTo(offset,
duration: Duration(seconds: 1), curve: Curves.linear);
};
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
scroll();
});
轮询算法
ListView
支持无限滑动后itemBuilder
回调下标Index
会超出数据源最大值。因此数据源也需要支持无限轮询来配合列表滚动。start
表示弹幕开始取值,这里设置为(0,1,2);index
表示itemBuilder
回调下标Index
。
int findIndex(int start, int index) {
index = start + index * 3;
if (expressList.length < index) {
index = index % (expressList.length - 1); // 取余
} else if (expressList.length == index) { // 是否是最后一个数据
index = start;
if (index >= expressList.length) { // 还需要判断数据源是否比start还小
index = (index % expressList.length - 1);
}
}
return index;
}
点击事件
一切都实现得很顺利最终就是弹幕点击实现。但实际上当ListView
的scrollController
在执行animateTo
时其实点击操作是失效的,ListView
无法响应点击事件。只有当animateTo
操作结束之后再执行点击才能执行点击。因此若要实现这个功能只能先将Timer
暂停再执行一次点击,再一次点击不可能是用户再去触发,这里只能采用模拟点击形式实现。
PS:ListView
无法响应点击事件具体原因还待研究,个人猜测列表做动画时对外部触摸事件进行了屏蔽处理。
GestureDetector(
onTapUp: (details){
// 点击抬起之后暂停定时器
_timer?.cancel();
// 模拟一次点击
Timer(Duration(milliseconds: 100),() {
GestureBinding.instance.handlePointerEvent(PointerAddedEvent(pointer: 0,position: details.globalPosition));
GestureBinding.instance.handlePointerEvent(PointerDownEvent(pointer: 0,position: details.globalPosition));
GestureBinding.instance.handlePointerEvent(PointerUpEvent(pointer: 0,position: details.globalPosition));
});
},
child: ListView.builder(
controller: scrollController,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: Common.getWidget(index),
onTap: () {
// 内部响应点击事件 然后重新设置定时器滚动列表
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
scroll();
});
},
);
},
),
);
来源:https://juejin.cn/post/7110108145292165150
猜你喜欢
- 简介这篇文章我一直在纠结到底要不要写,不想写一来因为定时器用法比较简单,二来是面试中也不常问。后来还是决定写了主要是想把自己分析问题思路分享
- SpringBoot自定义 * 和跨域配置冲突技术栈vue-cli3,springboot 2.3.2.RELEASE问题引出在做毕业设计过
- Java 中的 CyclicBarrier 是一种同步工具,它可以让多个线程在一个屏障处等待,直到所有线程都到达该屏障处后,才能继续执行。C
- Java里一个对象obj被创建时,被放在堆里。当GC运行的时候,发现没有任何引用指向obj,那么就会回收obj对象的堆内存空间。换句话说,一
- Maven打包时指定启动类使用Maven打包的时候, 有时候需要指定启动类, 可如下操作!测试项目(结构如下):代码: com.xxx.Ma
- 问题描述 idea启动tomcat后乱码了,并且,idea的各种编码都是设置的为UTF-8,但是中文就是乱码了。解决方法 进入idea的安装
- 本文实例讲述了Android编程使用WebView实现文件下载功能的两种方法。分享给大家供大家参考,具体如下:在应用中,通常会使用到文件下载
- Spring BeanPostProcessor执行顺序首先 Spring 通过调用构造方法创建 User 对象;User 对象创建好之后,
- Service的生命周期 (适用于2.1及以上)1. 被startService的无论是否有任何活动绑定到该Service,都在后台运行。o
- 背景最近再做一个需求,就是对站点的一些事件进行埋点,说白了就是记录用户的访问行为。那么这些数据怎么保存呢,人家点一下保存一下?显然不合适,肯
- 题目要求思路一:枚举 + 二分逐一枚举值域内的所有值,然后二分判断是否合法。Javaclass Solution { &nbs
- 需求输入手机号码,点击获取按钮,服务端接受请求发送短信用户输入验证码点击登录手机号码必须属于系统的注册用户,并且唯一手机号与验证码正确性及其
- 在 javax.validation.constraints包中定义了非常多的校验注解,引入依赖:<dependency> &n
- # 前言在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信
- 经常要检测某些IP地址范围段的计算机是否在线。有很多的方法,比如进入到网关的交换机上去查询、使用现成的工具或者编写一个简单的DOS脚本等等,
- 本文实例讲述了WinForm调用jar包的方法。分享给大家供大家参考,具体如下:因为工作需要,需要做一个数据上传的程序,客户规定的是:数据接
- 概述ConcurrentHashMap(CHM)是日常开发中使用频率非常高的一种数据结构,想对于普通的HashMap,CHM提供了线程安全的
- 引言Java * 机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理
- 获取和释放 monitor 锁的时机本文我们研究下 synchronized 背后的 monitor 锁。我们都知道,最简单的同步方式就是利
- 定义可理解为 适配广泛的类型,即参数化类型,可以把类型像方法的参数那样进行传递。// 以ArrayList为示例// 泛型T可以是任意类pu