flutter3_douyin:基于flutter3+dart3短视频直播实例|Flutter3.x仿抖音

2024-03-25 06:04

本文主要是介绍flutter3_douyin:基于flutter3+dart3短视频直播实例|Flutter3.x仿抖音,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

flutter3-dylive 跨平台仿抖音短视频直播app实战项目。

全新原创基于flutter3.19.2+dart3.3.0+getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块,直播间进场/礼物动效,聊天等模块。

在这里插入图片描述

运用技术

  • 编辑器:vscode
  • 技术框架:flutter3.19.2+dart3.3.0
  • 路由/状态插件:get: ^4.6.6
  • 缓存服务:get_storage: ^2.1.1
  • 图片预览插件:photo_view: ^0.14.0
  • 刷新加载:easy_refresh^3.3.4
  • toast轻提示:toast^0.3.0
  • 视频套件:media_kit: ^1.1.10+1

在这里插入图片描述
在这里插入图片描述
Flutter-dyLive实现了类似抖音全屏上下滑动、左右切换页面效果。

左右滑动的同时,顶部状态栏+Tab菜单+底部bottomNavigationBar导航栏三者联动效果。

在这里插入图片描述

在这里插入图片描述

目录结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本篇分享主要是短视频和直播模块,至于其它技术知识点,大家可以去看看之前分享的flutter3聊天实例文章。

https://blog.csdn.net/yanxinyun1990/article/details/136051099
https://blog.csdn.net/yanxinyun1990/article/details/136410049

flutter底部导航菜单

在这里插入图片描述
使用 bottomNavigationBar 组件实现底部导航页面模块切换。通过getx状态来联动控制底部导航栏背景颜色。

中间图标/图片按钮,使用了 Positioned 组件定位实现功能。

return Scaffold(backgroundColor: Colors.grey[50],body: pageList[pageCurrent],// 底部导航栏bottomNavigationBar: Theme(// Flutter去掉BottomNavigationBar底部导航栏的水波纹data: ThemeData(splashColor: Colors.transparent,highlightColor: Colors.transparent,hoverColor: Colors.transparent,),child: Obx(() {return Stack(children: [Container(decoration: const BoxDecoration(border: Border(top: BorderSide(color: Colors.black54, width: .1)),),child: BottomNavigationBar(backgroundColor: bottomNavigationBgcolor(),fixedColor: FStyle.primaryColor,unselectedItemColor: bottomNavigationItemcolor(),type: BottomNavigationBarType.fixed,elevation: 1.0,unselectedFontSize: 12.0,selectedFontSize: 12.0,currentIndex: pageCurrent,items: [...pageItems],onTap: (index) {setState(() {pageCurrent = index;});},),),// 自定义底部导航栏中间按钮Positioned(left: MediaQuery.of(context).size.width / 2 - 15,top: 0,bottom: 0,child: InkWell(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// Icon(Icons.tiktok, color: bottomNavigationItemcolor(centerDocked: true), size: 32.0,),Image.asset('assets/images/applogo.png', width: 32.0, fit: BoxFit.contain,)// Text('直播', style: TextStyle(color: bottomNavigationItemcolor(centerDocked: true), fontSize: 12.0),)],),onTap: () {setState(() {pageCurrent = 2;});},),),],);}),),
);
import 'package:flutter/material.dart';
import 'package:get/get.dart';import '../styles/index.dart';
import '../../controllers/page_video_controller.dart';// 引入pages页面
import '../pages/index/index.dart';
import '../pages/video/index.dart';
import '../pages/live/index.dart';
import '../pages/message/index.dart';
import '../pages/my/index.dart';class Layout extends StatefulWidget {const Layout({super.key});State<Layout> createState() => _LayoutState();
}class _LayoutState extends State<Layout> {PageVideoController pageVideoController = Get.put(PageVideoController());// page索引int pageCurrent = 0;// page页面List pageList = [const Index(), const FVideo(), const FLiveList(), const Message(), const My()];// tabs选项List pageItems = [const BottomNavigationBarItem(icon: Icon(Icons.home_outlined),label: '首页'),const BottomNavigationBarItem(icon: Icon(Icons.play_arrow_outlined),label: '短视频'),const BottomNavigationBarItem(icon: Icon(Icons.live_tv_rounded, color: Colors.transparent,),label: ''),BottomNavigationBarItem(icon: Stack(alignment: const Alignment(4, -2),children: [const Icon(Icons.messenger_outline),FStyle.badge(1)],),label: '消息'),BottomNavigationBarItem(icon: Stack(alignment: const Alignment(1.5, -1),children: [const Icon(Icons.person_outline),FStyle.badge(0, isdot: true)],),label: '我')];// 底部导航栏背景色Color bottomNavigationBgcolor() {int index = pageCurrent;int pageVideoTabIndex = pageVideoController.pageVideoTabIndex.value;Color color = Colors.white;if(index == 1) {if([1, 2, 3].contains(pageVideoTabIndex)) {color = Colors.white;}else {color = Colors.black;}}return color;}// 底部导航栏颜色Color bottomNavigationItemcolor({centerDocked = false}) {int index = pageCurrent;int pageVideoTabIndex = pageVideoController.pageVideoTabIndex.value;Color color = Colors.black54;if(index == 1) {if([1, 2, 3].contains(pageVideoTabIndex)) {color = Colors.black54;}else {color = Colors.white60;}}else if(index == 2 && centerDocked) {color = FStyle.primaryColor;}return color;}// ...
}

在这里插入图片描述

flutter3实现抖音沉浸式滑动

在这里插入图片描述
在这里插入图片描述
使用TabBar组件和PageView组件实现顶部菜单和页面联动切换效果。

return Scaffold(extendBodyBehindAppBar: true,appBar: AppBar(forceMaterialTransparency: true,backgroundColor: [1, 2, 3].contains(pageVideoController.pageVideoTabIndex.value) ? null : Colors.transparent,foregroundColor: [1, 2, 3].contains(pageVideoController.pageVideoTabIndex.value) ? Colors.black : Colors.white,titleSpacing: 1.0,leading: Obx(() => IconButton(icon: Icon(Icons.menu, color: tabColor(),), onPressed: () {},),),title: Obx(() {return TabBar(controller: tabController,tabs: pageTabs.map((v) => Tab(text: v)).toList(),isScrollable: true,tabAlignment: TabAlignment.center,overlayColor: MaterialStateProperty.all(Colors.transparent),unselectedLabelColor: unselectedTabColor(),labelColor: tabColor(),indicatorColor: tabColor(),indicatorSize: TabBarIndicatorSize.label,unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),labelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.w600),dividerHeight: 0,labelPadding: const EdgeInsets.symmetric(horizontal: 10.0),indicatorPadding: const EdgeInsets.symmetric(horizontal: 5.0),onTap: (index) {pageVideoController.updatePageVideoTabIndex(index); // 更新索引pageController.jumpToPage(index);},);}),actions: [Obx(() => IconButton(icon: Icon(Icons.search, color: tabColor(),), onPressed: () {},),),],),body: Column(children: [Expanded(child: Stack(children: [/// 水平滚动模块PageView(// 自定义滚动行为(支持桌面端滑动、去掉滚动条槽)scrollBehavior: PageScrollBehavior().copyWith(scrollbars: false),scrollDirection: Axis.horizontal,controller: pageController,onPageChanged: (index) {pageVideoController.updatePageVideoTabIndex(index); // 更新索引setState(() {tabController.animateTo(index);});},children: [...pageModules],),],),),],),
);
PageVideoController pageVideoController = Get.put(PageVideoController());List<String> pageTabs = ['热点', '长视频', '文旅', '商城', '关注', '同城服务', '推荐'];
final pageModules = [const HotModule(),const LongVideoModule(),const TripModule(),const MallModule(),const FavorModule(),const NearModule(),const RecommendModule()
];
late final TabController tabController = TabController(initialIndex: pageVideoController.pageVideoTabIndex.value, length: pageTabs.length, vsync: this);
// 页面controller
late final PageController pageController = PageController(initialPage: pageVideoController.pageVideoTabIndex.value, viewportFraction: 1.0);
void dispose() {tabController.dispose();pageController.dispose();super.dispose();
}

flutter实现短视频底部播放拖拽条

在这里插入图片描述
短视频底部又一条mini播放进度条,可实时显示视频播放进度,可拖拽到指定播放时间点。

// flutter滑动短视频模块  Q:282310962return Container(color: Colors.black,child: Column(children: [Expanded(child: Stack(children: [/// 垂直滚动模块PageView.builder(// 自定义滚动行为(支持桌面端滑动、去掉滚动条槽)scrollBehavior: PageScrollBehavior().copyWith(scrollbars: false),scrollDirection: Axis.vertical,controller: pageController,onPageChanged: (index) async {...},itemCount: videoList.length,itemBuilder: (context, index) {return Stack(children: [// 视频区域Positioned(top: 0,left: 0,right: 0,bottom: 0,child: GestureDetector(child: Stack(children: [// 短视频插件Visibility(visible: videoIndex == index,child: Video(controller: videoController,fit: BoxFit.cover,// 无控制条controls: NoVideoControls,),),// 播放/暂停按钮StreamBuilder(stream: player.stream.playing,builder: (context, playing) {return Visibility(visible: playing.data == false,child: Center(child: IconButton(padding: EdgeInsets.zero,onPressed: () {player.playOrPause();},icon: Icon(playing.data == true ? Icons.pause : Icons.play_arrow_rounded,color: Colors.white70,size: 70,),),),);},),],),onTap: () {player.playOrPause();},),),// 右侧操作栏Positioned(bottom: 15.0,right: 10.0,child: Column(...),),// 底部信息区域Positioned(bottom: 15.0,left: 10.0,right: 80.0,child: Column(...),),// 播放mini进度条Positioned(bottom: 0.0,left: 10.0,right: 10.0,child: Visibility(visible: videoIndex == index && position > Duration.zero,child: Listener(child: SliderTheme(data: const SliderThemeData(trackHeight: 2.0,thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0), // 调整滑块的大小// trackShape: RectangularSliderTrackShape(), // 使用矩形轨道形状overlayShape: RoundSliderOverlayShape(overlayRadius: 0), // 去掉Slider默认上下边距间隙inactiveTrackColor: Colors.white24, // 设置非活动进度条的颜色activeTrackColor: Colors.white, // 设置活动进度条的颜色thumbColor: Colors.pinkAccent, // 设置滑块的颜色overlayColor: Colors.transparent, // 设置滑块覆盖层的颜色),child: Slider(value: sliderValue,onChanged: (value) async {// debugPrint('当前视频播放时间$value');setState(() {sliderValue = value;});// 跳转播放时间await player.seek(duration * value.clamp(0.0, 1.0));},onChangeEnd: (value) async {setState(() {sliderDraging = false;});// 继续播放if(!player.state.playing) {await player.play();}},),),onPointerMove: (e) {setState(() {sliderDraging = true;});},),),),// 视频播放时间Positioned(bottom: 90.0,left: 10.0,right: 10.0,child: Visibility(visible: sliderDraging,child: Row(mainAxisAlignment: MainAxisAlignment.center,children: [Text(position.label(reference: duration), style: const TextStyle(color: Colors.white, fontSize: 16.0, fontWeight: FontWeight.w600),),Container(margin: const EdgeInsets.symmetric(horizontal: 7.0),child: const Text('/', style: TextStyle(color: Colors.white54, fontSize: 10.0,),),),Text(duration.label(reference: duration), style: const TextStyle(color: Colors.white54, fontSize: 16.0, fontWeight: FontWeight.w600),),],),),),],);},),/// 固定层// 红包Positioned(left: 15.0,top: MediaQuery.of(context).padding.top + 20,child: Container(height: 40.0,width: 40.0,decoration: BoxDecoration(color: Colors.black12,borderRadius: BorderRadius.circular(100.0),),child: UnconstrainedBox(child: Image.asset('assets/images/time-hb.png', width: 30.0, fit: BoxFit.contain,),),),),],),),],),
);

flutter3直播模块

在这里插入图片描述
在这里插入图片描述

// 商品购买动效
Container(...
),// 加入直播间动效
const AnimationLiveJoin(joinQueryList: [{'avatar': 'assets/images/logo.png', 'name': 'andy'},{'avatar': 'assets/images/logo.png', 'name': 'jack'},{'avatar': 'assets/images/logo.png', 'name': '一条咸鱼'},{'avatar': 'assets/images/logo.png', 'name': '四季平安'},{'avatar': 'assets/images/logo.png', 'name': '叶子'},],
),// 送礼物动效
const AnimationLiveGift(giftQueryList: [{'label': '小心心', 'gift': 'assets/images/gift/gift1.png', 'user': 'Jack', 'avatar': 'assets/images/avatar/uimg2.jpg', 'num': 12},{'label': '棒棒糖', 'gift': 'assets/images/gift/gift2.png', 'user': 'Andy', 'avatar': 'assets/images/avatar/uimg6.jpg', 'num': 36},{'label': '大啤酒', 'gift': 'assets/images/gift/gift3.png', 'user': '一条咸鱼', 'avatar': 'assets/images/avatar/uimg1.jpg', 'num': 162},{'label': '人气票', 'gift': 'assets/images/gift/gift4.png', 'user': 'Flower', 'avatar': 'assets/images/avatar/uimg5.jpg', 'num': 57},{'label': '鲜花', 'gift': 'assets/images/gift/gift5.png', 'user': '四季平安', 'avatar': 'assets/images/avatar/uimg3.jpg', 'num': 6},{'label': '捏捏小脸', 'gift': 'assets/images/gift/gift6.png', 'user': 'Alice', 'avatar': 'assets/images/avatar/uimg4.jpg', 'num': 28},{'label': '你真好看', 'gift': 'assets/images/gift/gift7.png', 'user': '叶子', 'avatar': 'assets/images/avatar/uimg7.jpg', 'num': 95},{'label': '亲吻', 'gift': 'assets/images/gift/gift8.png', 'user': 'YOYO', 'avatar': 'assets/images/avatar/uimg8.jpg', 'num': 11},{'label': '玫瑰', 'gift': 'assets/images/gift/gift12.png', 'user': '宇辉', 'avatar': 'assets/images/avatar/uimg9.jpg', 'num': 3},{'label': '私人飞机', 'gift': 'assets/images/gift/gift16.png', 'user': 'Hison', 'avatar': 'assets/images/avatar/uimg10.jpg', 'num': 273},],
),// 直播弹幕+商品讲解
Container(margin: const EdgeInsets.only(top: 7.0),height: 200.0,child: Row(crossAxisAlignment: CrossAxisAlignment.end,children: [Expanded(child: ListView.builder(padding: EdgeInsets.zero,itemCount: liveJson[index]['message']?.length,itemBuilder: (context, i) => danmuList(liveJson[index]['message'])[i],),),SizedBox(width: isVisibleGoodsTalk ? 7 : 35,),// 商品讲解Visibility(visible: isVisibleGoodsTalk,child: Column(...),),],),
),// 底部工具栏
Container(margin: const EdgeInsets.only(top: 7.0),child: Row(...),
),

直播间从右到左进入直播间动画及礼物左侧滑入效果,通过 SlideTransition 组件实现进场动画。

return SlideTransition(position: animationFirst ? animation : animationMix,child: Container(alignment: Alignment.centerLeft,margin: const EdgeInsets.only(top: 7.0),padding: const EdgeInsets.symmetric(horizontal: 7.0,),height: 23.0,width: 250,decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.centerLeft,end: Alignment.centerRight,colors: [Color(0xFF6301FF), Colors.transparent],),borderRadius: BorderRadius.horizontal(left: Radius.circular(10.0)),),child: joinList!.isNotEmpty ? Text('欢迎 ${joinList![0]['name']} 加入直播间', style: const TextStyle(color: Colors.white, fontSize: 14.0,),):Container(),),
);
class _AnimationLiveJoinState extends State<AnimationLiveJoin> with TickerProviderStateMixin {// 动画控制器late AnimationController controller = AnimationController(vsync: this,duration: const Duration(milliseconds: 500), // 第一个动画持续时间);late AnimationController controllerMix = AnimationController(vsync: this,duration: const Duration(milliseconds: 1000), // 第二个动画持续时间);// 动画late Animation<Offset> animation = Tween(begin: const Offset(2.5, 0), end: const Offset(0, 0)).animate(controller);late Animation<Offset> animationMix = Tween(begin: const Offset(0, 0), end: const Offset(-2.5, 0)).animate(controllerMix);Timer? timer;// 是否第一个动画bool animationFirst = true;// 是否空闲bool idle = true;// 加入直播间数据列表List? joinList;void initState() {super.initState();joinList = widget.joinQueryList!.toList();runAnimation();animation.addListener(() {if(animation.status == AnimationStatus.forward) {debugPrint('第一个动画进行中');idle = false;setState(() {});}else if(animation.status == AnimationStatus.completed) {debugPrint('第一个动画结束');animationFirst = false;if(controllerMix.isCompleted || controllerMix.isDismissed) {timer = Timer(const Duration(seconds: 2), () {controllerMix.forward();debugPrint('第二个动画开始');});}setState(() {});}});animationMix.addListener(() {if(animationMix.status == AnimationStatus.forward) {setState(() {});}else if(animationMix.status == AnimationStatus.completed) {animationFirst = true;controller.reset();controllerMix.reset();if(joinList!.isNotEmpty) {joinList!.removeAt(0);}idle = true;// 执行下一个数据runAnimation();setState(() {});}});}void runAnimation() {if(joinList!.isNotEmpty) {// 空闲状态才能执行,防止添加数据播放状态混淆if(idle == true) {if(controller.isCompleted || controller.isDismissed) {setState(() {});timer = Timer(Duration.zero, () {controller.forward();});}}}}void dispose() {controller.dispose();controllerMix.dispose();timer?.cancel();super.dispose();}}

另外项目中还加入了之前flutter3聊天功能模块。

Ok,综上就是flutter3+dart3仿抖音app实例的一些技术知识分享,希望对大家有所帮助。

最后附上两个实例项目

  • uni-app+vue3+vk-uview多端直播商城项目
    https://blog.csdn.net/yanxinyun1990/article/details/135329724

  • vue3+vite4中后台管理系统
    https://blog.csdn.net/yanxinyun1990/article/details/130144212

在这里插入图片描述

这篇关于flutter3_douyin:基于flutter3+dart3短视频直播实例|Flutter3.x仿抖音的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/844054

相关文章

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

MySQL的索引失效的原因实例及解决方案

《MySQL的索引失效的原因实例及解决方案》这篇文章主要讨论了MySQL索引失效的常见原因及其解决方案,它涵盖了数据类型不匹配、隐式转换、函数或表达式、范围查询、LIKE查询、OR条件、全表扫描、索引... 目录1. 数据类型不匹配2. 隐式转换3. 函数或表达式4. 范围查询之后的列5. like 查询6

Python开发围棋游戏的实例代码(实现全部功能)

《Python开发围棋游戏的实例代码(实现全部功能)》围棋是一种古老而复杂的策略棋类游戏,起源于中国,已有超过2500年的历史,本文介绍了如何用Python开发一个简单的围棋游戏,实例代码涵盖了游戏的... 目录1. 围棋游戏概述1.1 游戏规则1.2 游戏设计思路2. 环境准备3. 创建棋盘3.1 棋盘类

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss