带来一款春节期间自研的Flutter3.27+Dart3.6
跨平台仿抖音App短视频+直播商城+聊天项目。
整合了短视频+直播+聊天三大功能模块。
实现了类似抖音App首页联动效果,上下滑动短视频、左右切换页面模块。
使用最新版跨平台框架Flutter3.27
开发搭建项目模板。
/// 入口文件main.dart
library;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:media_kit/media_kit.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'utils/common.dart';
// 引入布局页面
import 'layouts/index.dart';
// 引入路由配置
import 'router/index.dart';
void main() async {
// 初始化get_storage存储
await GetStorage.init();
// 初始化media_kit视频套件
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter3 DYMALL',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFFF9900)),
useMaterial3: true,
fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null
),
home: const Layout(),
// 初始化路由
initialRoute: Common.isLogin() ? '/' : '/login',
// 路由页面
getPages: routePages,
// 初始化弹窗key
navigatorKey: MyDialog.navigatorKey,
);
}
}
实现一个如下图所示,轮播图延伸到导航条,滚动页面,tab栏自动吸附顶部。
采用CustomScrollView
滚动,SliverAppBar
实现顶部轮播图功能,SliverPersistentHeader
实现tab固定吸附效果。
return Scaffold(
backgroundColor: Colors.grey[50],
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
controller: scrollController,
slivers: [
SliverAppBar(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
pinned: true,
expandedHeight: 200.0,
titleSpacing: 10.0,
// 搜索框(高斯模糊背景)
title: ClipRRect(
borderRadius: BorderRadius.circular(30.0),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
...
),
),
),
actions: [
IconButton(icon: Icon(Icons.shopping_cart_outlined), onPressed: () {},),
],
// 自定义伸缩区域(轮播图)
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFFF5000), Color(0xFFfcaec4)
]
)
),
child: FlexibleSpaceBar(
background: Swiper.children(
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
)
),
indicatorLayout: PageIndicatorLayout.SCALE,
children: [
Image.network('https://m.360buyimg.com/babel/jfs/t20271217/224114/35/38178/150060/6760d559Fd654f946/968c156726b6e822.png',),
Image.network('https://m.360buyimg.com/babel/jfs/t20280117/88832/5/48468/139826/6789cbcfF4e0b2a3d/9dc54355b6f65c40.jpg',),
Image.network('https://m.360buyimg.com/babel/jfs/t20280108/255505/29/10540/137372/677ddbc1F6cdbbed0/bc477fadedef22a8.jpg',),
],
),
),
),
),
...
// tabbar列表
SliverPersistentHeader(
pinned: true,
delegate: CustomStickyHeader(
child: PreferredSize(
preferredSize: Size.fromHeight(45.0),
child: Container(
...
),
),
),
),
// 瀑布流列表
...
],
),
),
// 返回顶部
floatingActionButton: Backtop(controller: scrollController, offset: scrollOffset),
);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
body: pageList[pageCurrent],
// 底部导航栏
bottomNavigationBar: Theme(
data: ThemeData(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
),
child: Obx(() {
return Stack(
children: [
Container(
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.black45, 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: [
...navItems
],
onTap: (index) {
setState(() {
pageCurrent = index;
});
},
),
),
// 自定义导航栏中间按钮
Positioned(
left: MediaQuery.of(context).size.width / 2 - 18,
top: 0,
bottom: 0,
child: InkWell(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/logo.png', width: 36.0, isAntiAlias: true, fit: BoxFit.contain,),
],
),
onTap: () {
setState(() {
pageCurrent = 2;
});
},
),
),
],
);
}),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
extendBodyBehindAppBar: true,
appBar: AppBar(
forceMaterialTransparency: true,
backgroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? null : Colors.transparent,
foregroundColor: [0, 1, 4, 5].contains(videoModuleController.videoTabIndex.value) ? Colors.black : Colors.white,
titleSpacing: 1.0,
leading: Obx(() => IconButton(
icon: Badge.count(
backgroundColor: Colors.red,
count: 6,
child: Icon(Icons.sort_rounded, color: tabColor(),),
),
onPressed: () {
// 自定义打开右侧drawer
scaffoldKey.currentState?.openDrawer();
},
)),
title: Obx(() {
return ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: TabBar(
...
),
);
}),
actions: [
Obx(() => IconButton(icon: Icon(Icons.search_rounded, color: tabColor(),), onPressed: () {},),),
],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: PageView(
controller: pageController,
onPageChanged: (index) {
videoModuleController.updateVideoTabIndex(index);
setState(() {
tabController.animateTo(index, duration: Duration(milliseconds: 200), curve: Curves.easeInOut);
});
},
children: [
...tabModules
],
),
),
// 侧边栏
drawer: Drawer(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(right: Radius.circular(15.0))),
clipBehavior: Clip.antiAlias,
width: 300,
child: Container(
...
),
),
);
}
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
VideoModuleController videoModuleController = Get.put(VideoModuleController());
late TabController tabController = TabController(initialIndex: videoModuleController.videoTabIndex.value, length: tabList.length, vsync: this);
late PageController pageController = PageController(initialPage: videoModuleController.videoTabIndex.value, viewportFraction: 1.0);
List<String> tabList = ['订阅', '逛逛', '直播', '团购', '短剧', '关注', '同城', '精选'];
final tabModules = [
KeepAliveWrapper(child: SubscribeModule()),
KeepAliveWrapper(child: BrowseModule()),
KeepAliveWrapper(child: LiveModule()),
KeepAliveWrapper(child: BuyingModule()),
KeepAliveWrapper(child: DramaModule()),
AttentionModule(),
LocalModule(),
RecommendModule()
];
class KeepAliveWrapper extends StatefulWidget {
final Widget child;
const KeepAliveWrapper({super.key, required this.child});
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
@override
bool get wantKeepAlive => true;
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Column(
children: [
Expanded(
child: Stack(
children: [
PageView.builder(
scrollDirection: Axis.vertical,
controller: pageController,
onPageChanged: (index) async {
// 更新播放索引
videoModuleController.updateVideoPlayIndex(index);
setState(() {
// 重置slider参数
sliderValue = 0.0;
sliderDraging = false;
position = Duration.zero;
duration = Duration.zero;
});
player.stop();
await player.open(Media(videoList[index]['src']));
},
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: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
child: Video(
controller: videoController,
fit: BoxFit.cover,
),
),
// 播放/暂停按钮
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.white60,
size: 80,
),
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))
),
),
),
);
},
),
],
),
onTap: () {
player.playOrPause();
},
),
),
// 右侧操作栏
Positioned(
bottom: 15.0,
right: 6.0,
child: Column(
spacing: 15.0,
children: [
...
],
),
),
// 底部信息区域
Positioned(
bottom: 15.0,
left: 10.0,
right: 80.0,
child: Column(
...
),
),
// mini播放进度条
Positioned(
bottom: 0.0,
left: 6.0,
right: 6.0,
child: Visibility(
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
child: Listener(
child: SliderTheme(
data: SliderThemeData(
trackHeight: sliderDraging ? 6.0 : 2.0,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0), // 调整滑块的大小
overlayShape: RoundSliderOverlayShape(overlayRadius: 0), // 去掉Slider默认上下边距间隙
inactiveTrackColor: Colors.white24, // 设置非活动进度条的颜色
activeTrackColor: Colors.white, // 设置活动进度条的颜色
thumbColor: Colors.white, // 设置滑块的颜色
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: 100.0,
left: 10.0,
right: 10.0,
child: Visibility(
visible: sliderDraging,
child: DefaultTextStyle(
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.0,
children: [
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
Text('/', style: TextStyle(fontSize: 14.0)),
Text(duration.label(reference: duration)),
],
),
)
),
),
],
);
},
),
/// 固定层
// 红包广告
Ads(),
],
),
),
],
),
);
}
Electron32桌面端os系统:https://cloud.tencent.com/developer/article/2449406
electron31+vue3客户端聊天Exe实例:https://cloud.tencent.com/developer/article/2435159
tauri2.0+vue3客户端admin后台系统:https://cloud.tencent.com/developer/article/2458392
uniapp+vue3仿携程预订客房模板:https://cloud.tencent.com/developer/article/2474766
OK,以上就是flutter3.27实战抖音app商城的一些知识分享,希望对大家有所帮助!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。