近年来,短视频平台(如抖音、TikTok)已经成为主流的内容消费方式。本文将分享一个用 Flutter 实现的 竖向滑动短视频播放器,支持自动播放、滑动切换、视频信息展示等核心功能。
功能概述
- 竖向 PageView 滑动切换视频
- 自动播放当前视频,暂停非当前视频
- 视频信息展示(作者头像、昵称、标题、音乐信息等)
- 视频互动按钮(点赞、评论、转发等)
- 黑色沉浸式 UI
效果类似 TikTok/抖音。
项目结构
bash
lib/
app/ # 应用入口
packages/app_ui/ # 全局主题样式
reels/ # 短视频模块
bloc/ # 状态管理(Bloc)
model/ # 数据模型与仓库
reel/ # 单条视频展示
view/ # 视频列表(PageView)
1. 应用入口与主题
入口 main.dart
:
dart
import 'package:reel_views/app/app.dart';
import 'package:reel_views/bootstrap.dart';
void main() {
bootstrap(() => const App());
}
应用 App
中使用 Bloc 提供 PostsRepository
和 ReelBloc
,并应用自定义暗色主题 AppDarkTheme
:
dart
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return RepositoryProvider(
create: (context) => PostsRepository(),
child: MaterialApp(
theme: const AppDarkTheme().theme,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: BlocProvider(
create: (context) => ReelBloc(
postsRepository: context.read<PostsRepository>()
),
child: const ReelsView(),
),
),
);
}
}
2. 数据模型与仓库
短视频数据来自 PostsRepository
:
dart
class PostsRepository {
static final recommendedReels = [
PostReelBlock(
author: PostAuthor.randomConfirmed(),
id: "1",
caption: "送你一朵小红花",
media: 'assets/video/Butterfly-209.mp4'
),
...
];
}
作者信息用 PostAuthor
封装,并支持随机生成测试数据:
dart
class PostAuthor {
const PostAuthor({
required this.id,
required this.avatarUrl,
required this.username,
this.isConfirmed = false
});
factory PostAuthor.randomConfirmed() {
final randomUser = _confirmedUsers[Random().nextInt(_confirmedUsers.length)];
return PostAuthor(
id: randomUser.id,
username: randomUser.username!,
avatarUrl: randomUser.avatarUrl!,
isConfirmed: true,
);
}
}
3. 状态管理(Bloc)
ReelBloc
负责视频数据加载:
dart
class ReelBloc extends Bloc<ReelEvent, ReelState> {
ReelBloc({ required PostsRepository postsRepository })
: _postsRepository = postsRepository,
super(const ReelState.initial()) {
on<ReelRecommendedPostsPageRequested>(_onReelRecommendedPostsPageRequested);
}
Future<void> _onReelRecommendedPostsPageRequested(
ReelRecommendedPostsPageRequested event,
Emitter<ReelState> emit,
) async {
emit(state.loading());
final recommendedBlocks = [...PostsRepository.recommendedReels..shuffle()];
emit(state.populated(blocks: recommendedBlocks));
}
}
4. 视频列表页面(竖向 PageView)
ReelsView
使用 PageView.builder
实现竖向滑动:
dart
class _ReelsViewState extends State<ReelsView> {
late PageController _pageController;
late ValueNotifier<int> _currentIndex;
@override
void initState() {
super.initState();
_pageController = PageController(keepPage: false);
_currentIndex = ValueNotifier(0);
context.read<ReelBloc>().add(const ReelRecommendedPostsPageRequested());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ReelBloc, ReelState>(
builder: (context, state) {
final blocks = state.blocks;
return PageView.builder(
controller: _pageController,
scrollDirection: Axis.vertical,
onPageChanged: (index) => _currentIndex.value = index,
itemCount: blocks.length,
itemBuilder: (context, index) {
final block = blocks[index];
final isCurrent = index == _currentIndex.value;
return Reel(
key: ValueKey(block.id),
play: isCurrent,
block: block,
);
},
);
},
);
}
}
5. 单条视频展示
Reel
组件负责渲染单条视频及 UI 叠层:
dart
class Reel extends StatefulWidget {
const Reel({ required this.block, required this.play, super.key });
final PostReelBlock block;
final bool play;
@override
State<Reel> createState() => _ReelState();
}
class _ReelState extends State<Reel> {
late VideoPlayerController _videoController;
@override
void initState() {
super.initState();
_videoController = VideoPlayerController.asset(widget.block.media);
}
@override
Widget build(BuildContext context) {
return InlineVideo(
shouldPlay: widget.play,
videoPlayerController: _videoController,
stackedWidget: Stack(
children: [
VerticalButtons(widget.block),
// 这里省略底部作者和标题信息布局
],
),
);
}
}
6. 视频播放器封装
InlineVideo
对 video_player
进行了封装,实现自动播放/暂停:
dart
class InlineVideo extends StatefulWidget {
const InlineVideo({
required this.shouldPlay,
required this.stackedWidget,
required this.videoPlayerController,
super.key
});
@override
State<InlineVideo> createState() => _InlineVideoState();
}
class _InlineVideoState extends State<InlineVideo> {
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_controller = widget.videoPlayerController;
_controller.initialize().then((_) {
if (widget.shouldPlay) _controller..play()..setLooping(true);
});
}
@override
void didUpdateWidget(covariant InlineVideo oldWidget) {
if (oldWidget.shouldPlay != widget.shouldPlay) {
widget.shouldPlay ? _controller.play() : _controller.pause();
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
widget.stackedWidget
],
);
}
}
7. 视频交互按钮
右侧互动按钮由 VerticalButtons
实现:
dart
class VerticalButtons extends StatelessWidget {
const VerticalButtons(this.block, {super.key});
final PostReelBlock block;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomRight,
child: Column(
children: [
const VerticalGroup(icon: Icons.favorite_outline, statisticCount: 30),
VerticalGroup(
statisticCount: 2,
child: SvgPicture.asset('assets/icons/chat_circle.svg', width: 30),
),
const VerticalGroup(icon: Icons.more_vert_sharp, withStatistic: false),
CircleAvatar(backgroundImage: NetworkImage(block.author.avatarUrl)),
],
),
);
}
}
总结与优化方向
本文用 Flutter + Bloc + video_player 实现了一个竖向短视频播放器,具备 TikTok 类似的交互体验。
未来可以优化的方向:
- 视频缓存与预加载:减少切换时的延迟
- 网络视频支持:支持从 API 拉取视频
- 手势交互:双击点赞、长按暂停
- 性能优化:更细粒度的资源释放与控制
你可以直接在此基础上添加更多功能,打造属于自己的短视频应用。