Flutter 实现类似抖音/TikTok 的竖向滑动短视频播放器

近年来,短视频平台(如抖音、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 提供 PostsRepositoryReelBloc,并应用自定义暗色主题 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. 视频播放器封装

InlineVideovideo_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 类似的交互体验。

未来可以优化的方向:

  1. 视频缓存与预加载:减少切换时的延迟
  2. 网络视频支持:支持从 API 拉取视频
  3. 手势交互:双击点赞、长按暂停
  4. 性能优化:更细粒度的资源释放与控制

你可以直接在此基础上添加更多功能,打造属于自己的短视频应用。


源码下载

github.com/wutao23yzd/...

相关推荐
编程乐学8 分钟前
网络资源模板--基于Android Studio 实现的通讯录App
android·android studio·移动端开发·通讯录app·安卓大作业
bytebeats2 小时前
# Android Studio Narwhal Agent 模式简介
android·android studio
GeniuswongAir2 小时前
iOS 26 一键登录失效:三大运营商 SDK 无法正常获取手机号
ios
SoaringHeart2 小时前
Flutter进阶:高内存任务的动态并发执行完美实现
前端·flutter
bytebeats2 小时前
Jetpack Compose 1.8 新增了 12 个新特性
android·android jetpack
limingade3 小时前
手机实时提取SIM卡打电话的信令声音-整体解决方案规划
android·智能手机·usb蓝牙·手机拦截电话通话声音
Harry技术3 小时前
Trae搭建Android开发:项目中Ktor的引入与使用实践
android·kotlin·trae
猪哥帅过吴彦祖3 小时前
Flutter 插件工作原理深度解析:从 Dart 到 Native 的完整调用链路
android·flutter·ios
归辞...3 小时前
「iOS」————UITableView性能优化
ios·性能优化·cocoa