0


开源Flutter3.19仿抖音实战(开发自己的抖音APP)

** 题记

—— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。**

开源项目Flutter3.19仿抖音实战

最近有空的时候用flutter3做了一个抖音的项目,方便大家学习,提供源码,有操作思路。原创开发基于flutter3.19.5+dart3.3.3+getx等技术开发仿抖音app实战项目。实现了类似抖音整屏丝滑式上下滑动视频、左右滑动切换页面模块,包含商城、购物车、支付功能等模块。
同时接入了友盟SDK统计数据,用户下载安装,活跃量,次日留存等等。

页面布局,逻辑思路进行了多次迭代与优化,学习flutter开发app,必须要看的实战项目:

学习目标

Flutter进阶高手分为三个阶段,从易到难,学习完成后,可以使用Flutter来开发独立的APP,适用于Android、iOS双平台的应用程序。

第一阶段是 Flutter开发必备Dart基础

第二个阶段是 Flutter核心技术, 一次性掌握,组件大全、页面布局、路由、网络请求、数据缓存、动画等等

第三个阶段是 开发实战企业级APP

flutter运用技术
编辑器:vscode
技术框架:flutter3.19.5+dart3.3.3
路由/状态插件:get: ^4.6.6
网络数据:dio: ^5.3.3
缓存服务:shared_preferences: ^2.2.1
图片预览插件:photo_view: ^0.14.0
刷新加载:easy_refresh^3.3.4
toast轻提示:toast^0.3.0
视频播放器:video_player: ^2.8.3
视频播放器: chewie: ^1.7.5

.......等等

实现启动页与自定义开屏广告,可换成穿山甲广告实现收益:

flutter3.19.x仿抖音教你开发商业级APP

接入字节跳动穿山甲广告

  1. await FlutterUnionad.register(
  2. androidAppId: "5098580",
  3. //穿山甲广告 Android appid 必填
  4. iosAppId: "5098580",
  5. //穿山甲广告 ios appid 必填
  6. useTextureView: true,
  7. //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView 选填
  8. appName: "unionad_test",
  9. //appname 必填
  10. allowShowNotify: true,
  11. //是否允许sdk展示通知栏提示 选填
  12. allowShowPageWhenScreenLock: true,
  13. //是否在锁屏场景支持展示广告落地页 选填
  14. debug: true,
  15. //测试阶段打开,可以通过日志排查问题,上线时去除该调用 选太难
  16. supportMultiProcess: true,
  17. //是否支持多进程,true支持 选填
  18. directDownloadNetworkType: [
  19. FlutterUnionad.NetCode.NETWORK_STATE_2G,
  20. FlutterUnionad.NetCode.NETWORK_STATE_3G,
  21. FlutterUnionad.NetCode.NETWORK_STATE_4G,
  22. FlutterUnionad.NetCode.NETWORK_STATE_WIFI
  23. ]); //允许直接下载的网络状态集合 选填//允许直接下载的网络状态集合 选填
flutter3.19.x+getx实现了类似抖音全屏上下滑动、左右切换页面效果:

使用 bottomNavigationBar 组件实现底部导航页面模块切换

flutter3.19.x+getx仿抖音教你开发商业级AP

flutter3.19.x+getx仿抖音教你开发商业级AP

视频页面布局,使用了 Stack 组件定位实现页面布局。

  1. return Scaffold(
  2. backgroundColor: Colors.black,
  3. body: SafeArea(
  4. child: GetBuilder<VideoController>(builder: (c) {
  5. return controller.isLoading
  6. ? const Center(child: CircularProgressIndicator())
  7. : controller.videos.isEmpty
  8. ? Center(
  9. child: Column(
  10. mainAxisSize: MainAxisSize.min,
  11. children: [
  12. const Padding(
  13. padding: EdgeInsets.only(bottom: 8.0),
  14. child: Text("暂无数据"),
  15. ),
  16. ElevatedButton(
  17. style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.red)),
  18. onPressed: controller.initLoad,
  19. child: const Text("刷新"),
  20. ),
  21. ],
  22. ))
  23. : PageView.builder(
  24. controller: PageController(
  25. initialPage: controller.currentIndex,
  26. viewportFraction: 1,
  27. ),
  28. itemCount: controller.videos.length,
  29. onPageChanged: (index) {
  30. controller.changeVideo(index);
  31. },
  32. scrollDirection: Axis.vertical,
  33. itemBuilder: (context, index) {
  34. return videoCard(controller.videos[index]);
  35. },
  36. );
  37. }),
  38. ),

使用TabBar组件和PageView组件实现顶部菜单和页面联动切换效果

  1. Obx(
  2. () => Scaffold(
  3. body: IndexedStack(
  4. index: homeController.currentIndex.value,
  5. children: [
  6. IndexPage(),
  7. homeController.currentIndex.value == 1 ? VideoPage() : Container(),
  8. Container(),
  9. MessagePage(),
  10. MinePage(),
  11. ],
  12. ),
  13. bottomNavigationBar: Theme(
  14. data: Theme.of(context).copyWith(
  15. splashColor: Colors.transparent,
  16. highlightColor: Colors.transparent,
  17. ),
  18. child: BottomNavigationBar(
  19. elevation: 0,
  20. iconSize: 24,
  21. backgroundColor: homeController.currentIndex.value == 1 ? Colors.black : Colors.white,
  22. selectedItemColor: homeController.currentIndex.value == 1 ? Colors.white : Colors.black,
  23. unselectedItemColor: const Color(0xff999999),
  24. type: BottomNavigationBarType.fixed,
  25. currentIndex: homeController.currentIndex.value,
  26. unselectedFontSize: 16,
  27. selectedFontSize: 18,
  28. items: const [
  29. BottomNavigationBarItem(
  30. icon: SizedBox.shrink(),
  31. label: "首页",
  32. ),
  33. BottomNavigationBarItem(
  34. icon: SizedBox.shrink(),
  35. label: "视频",
  36. ),
  37. BottomNavigationBarItem(
  38. icon: Icon(
  39. Icons.add_box,
  40. size: 32,
  41. color: Colors.red,
  42. ),
  43. label: "",
  44. ),
  45. BottomNavigationBarItem(
  46. icon: SizedBox.shrink(),
  47. label: "消息",
  48. ),
  49. BottomNavigationBarItem(
  50. icon: SizedBox.shrink(),
  51. label: "我",
  52. ),
  53. ],
  54. onTap: (index) {
  55. homeController.onChangePage(index);
  56. },
  57. ),
  58. ),
  59. ),
  60. );

video_player基本使用

  1. /// 声明控制器
  2. late VideoPlayerController _controller;
  3. /// 初始化控制器
  4. _controller = VideoPlayerController.network(list[0]['video_url'])
  5. ///设置视频循环播放
  6. ..setLooping(true)
  7. ///设置监听
  8. ..addListener(() {
  9. setState(() {
  10. });
  11. })
  12. ///初始化
  13. ..initialize().then((_) async {
  14. ///初始化完成更新状态,不然播放器不会播放
  15. setState(() {
  16. playOrPauseVideo();
  17. });
  18. }).catchError((err) {
  19. ///播放出错
  20. print(err);
  21. });
  22. /// 显示视频
  23. SizedBox(
  24. height: 240,
  25. width: MediaQuery.of(context).size.width,
  26. child: _controller.value.isInitialized
  27. ? AspectRatio(
  28. aspectRatio:
  29. _controller.value.aspectRatio,
  30. child: VideoPlayer(_controller),
  31. )
  32. : const Text(
  33. "没有要播放的视频",
  34. style: TextStyle(color: Colors.red),
  35. ),
  36. ),
  1. 注意点:在播放器initialize 完后一定要更新播放器的状态,不然是widget拿不到状态改变,是不会播放的

视频播放,暂停

  1. /// 判断播放和暂停
  2. void playOrPauseVideo() {
  3. setState(() {
  4. if (_controller.value.isPlaying) {
  5. _controller.pause();
  6. } else {
  7. // If the video is paused, play it.
  8. _controller.play();
  9. }
  10. });
  11. }

实现抖音滑动效果

核心原理就是使用

  1. PageView

来实现的,需要注意的是每次滑动的时候需要将上一个_controller释放掉以后再重新创建一个,不然上个视频还是会播放的.具体代码如下:

  1. PageView.builder(
  2. physics: const QuickerScrollPhysics(),
  3. controller: _pageController,
  4. scrollDirection: Axis.vertical,
  5. itemCount: list.length,
  6. onPageChanged: (index) {
  7. _controller.dispose();
  8. _controller =
  9. VideoPlayerController.network(list[index]['video_url'])
  10. ..setLooping(true)
  11. ..addListener(() {
  12. setState(() {
  13. });
  14. })
  15. ..initialize().then((_) async {
  16. setState(() {
  17. playOrPauseVideo();
  18. });
  19. }).catchError((err) {
  20. print(err);
  21. });
  22. if (index == list.length - 1) {
  23. Future.delayed(
  24. const Duration(milliseconds: 200)).then((lwh) {
  25. _pageController.jumpToPage(0);
  26. });
  27. }
  28. },
  29. itemBuilder: (context, i) {
  30. return Stack(
  31. children: [
  32. /// 播放器view
  33. Container(
  34. color: Colors.black,
  35. child: Center(
  36. child: Stack(
  37. children: [
  38. AppNetImage(
  39. fit: BoxFit.fitWidth,
  40. imageUrl: list[i]['image_url'],
  41. height: 240,
  42. width: MediaQuery.of(context).size.width,
  43. ),
  44. Positioned(
  45. child: Stack(
  46. children: [
  47. InkWell(
  48. child: SizedBox(
  49. height: 240,
  50. width: MediaQuery.of(context).size.width,
  51. child: _controller.value.isInitialized
  52. ? AspectRatio(
  53. aspectRatio:
  54. _controller.value.aspectRatio,
  55. child: VideoPlayer(_controller),
  56. )
  57. : const Text(
  58. "没有要播放的视频",
  59. style: TextStyle(color: Colors.red),
  60. ),
  61. ),
  62. onTap: () {
  63. playOrPauseVideo();
  64. },
  65. ),
  66. ],
  67. )),
  68. Positioned(
  69. left: MediaQuery.of(context).size.width / 2 - 30,
  70. top: 90,
  71. child: _controller.value.isPlaying
  72. ? const SizedBox()
  73. : const Icon(
  74. Icons.play_arrow,
  75. color: Colors.white,
  76. size: 60,
  77. ),
  78. ),
  79. ],
  80. ),
  81. ),
  82. ),
  83. /// 显示全屏按钮
  84. Positioned(
  85. bottom: MediaQuery.of(context).padding.bottom + 100,
  86. right: 8,
  87. child: InkWell(
  88. child: const Icon(
  89. Icons.aspect_ratio,
  90. color: Colors.white,
  91. size: 30,
  92. ),
  93. onTap: () {
  94. _toggleFullScreen();
  95. },
  96. )),
  97. /// 显示进度条
  98. Positioned(
  99. bottom: MediaQuery.of(context).padding.bottom,
  100. child: SizedBox(
  101. width: MediaQuery.of(context).size.width,
  102. height: 1,
  103. child: VideoProgressIndicator(
  104. _controller,
  105. allowScrubbing: true,
  106. padding: const EdgeInsets.all(0),
  107. colors: const VideoProgressColors(
  108. playedColor: Colors.white, // 已播放的颜色
  109. bufferedColor:
  110. Color.fromRGBO(255, 255, 255, .5), // 缓存中的颜色
  111. backgroundColor:
  112. Color.fromRGBO(255, 255, 255, .3), // 为缓存的颜色
  113. ),
  114. ),
  115. ))
  116. ],
  117. );
  118. },
  119. )

flutter3.19.x+getx仿抖音教你开发商业级AP

常言道,学而不思则罔,思而不学则殆。在学习flutter时也应该多多思考,积极消化自己不会的知识,这也能强化我们的技术水平,帮助我们更好适应快节奏的开发进程,成为一名更有竞争力的Android开发者!

由于文件比较大,这里只是将部分截图出来,如果你觉得这些内容对你有帮助:

【扫描下方卡片即可免费领取!!!】


本文转载自: https://blog.csdn.net/weixin_44619684/article/details/138444747
版权归原作者 高级技术工程师 所有, 如有侵权,请联系我们删除。

“开源Flutter3.19仿抖音实战(开发自己的抖音APP)”的评论:

还没有评论