Flutter for OpenHarmony垃圾分类指南App实战:视频教程实现
摘要 本文介绍了在Flutter for OpenHarmony环境下实现视频教程页面的开发方法。页面采用ListView.builder高效渲染视频列表,通过Card组件实现卡片式布局,每张卡片包含视频缩略图(使用Stack组件叠加播放按钮和时长标签)和视频信息(标题、播放量等)。技术要点包括InkWell的水波纹点击反馈、Model类管理视频数据,以及视频时长、播放量等信息的展示优化。该设计以

前言
有些东西看文字不如看视频来得直观,比如塑料瓶怎么压扁、厨余垃圾怎么沥干水分。视频教程页面就是提供这类可视化学习内容的地方。本文将详细介绍如何在Flutter for OpenHarmony环境下实现一个完整的视频教程页面。视频作为一种直观的学习方式,能够帮助用户更好地理解垃圾分类的具体操作方法,特别是对于一些需要动手操作的分类技巧,视频演示比文字描述更加清晰易懂。
技术要点概览
本页面涉及的核心技术点包括以下几个方面:
- ListView.builder:高效的列表渲染,支持大量视频数据展示
- Stack组件:缩略图和播放按钮的叠加效果
- InkWell组件:点击时的水波纹反馈效果
- Card组件:卡片式布局设计,视觉效果美观
视频数据结构
每个视频包含标题、时长和播放量:
class VideoGuidePage extends StatelessWidget {
const VideoGuidePage({super.key});
Widget build(BuildContext context) {
final videos = [
{'title': '垃圾分类入门指南', 'duration': '05:30', 'views': '12.5万'},
{'title': '可回收物正确投放方法', 'duration': '03:45', 'views': '8.2万'},
{'title': '有害垃圾处理注意事项', 'duration': '04:20', 'views': '6.8万'},
{'title': '厨余垃圾减量小技巧', 'duration': '06:15', 'views': '9.1万'},
{'title': '家庭垃圾分类实操演示', 'duration': '08:00', 'views': '15.3万'},
{'title': '办公室垃圾分类指南', 'duration': '04:50', 'views': '5.6万'},
];
视频内容覆盖了从入门到进阶的各个方面,有基础知识也有实操演示。播放量数据能帮助用户判断哪些视频更受欢迎。
使用Model类管理数据
实际项目中建议使用Model类:
class VideoItem {
final String id;
final String title;
final String duration;
final int views;
final String? thumbnailUrl;
final String? videoUrl;
final String? category;
VideoItem({
required this.id,
required this.title,
required this.duration,
required this.views,
this.thumbnailUrl,
this.videoUrl,
this.category,
});
String get formattedViews {
if (views >= 10000) {
return '${(views / 10000).toStringAsFixed(1)}万';
}
return '$views';
}
}
页面布局
视频列表用卡片式布局,每张卡片左边是视频缩略图,右边是标题和播放量:
return Scaffold(
appBar: AppBar(title: const Text('视频教程')),
body: ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: videos.length,
itemBuilder: (context, index) {
final video = videos[index];
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: InkWell(
onTap: () => _showVideoDialog(context, video['title']!),
用InkWell包裹整个卡片,点击时会有水波纹效果,给用户明确的反馈。
视频缩略图区域
缩略图区域用Container模拟,中间放播放按钮,右下角显示时长:
child: Padding(
padding: EdgeInsets.all(12.w),
child: Row(
children: [
Container(
width: 120.w,
height: 80.h,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: Stack(
alignment: Alignment.center,
children: [
Icon(Icons.play_circle_fill, color: AppTheme.primaryColor, size: 40.sp),
设计说明:实际项目中这里应该是视频的真实缩略图。现在用纯色背景加播放图标来占位,视觉上也能让用户理解这是个视频入口。
时长标签
时长标签放在右下角:
Positioned(
bottom: 4.h,
right: 4.w,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
video['duration']!,
style: TextStyle(color: Colors.white, fontSize: 10.sp),
),
),
),
],
),
),
时长用半透明黑色背景,白色文字,这是视频平台通用的设计语言。
视频信息区域
右边显示视频标题和播放量:
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
video['title']!,
style: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.w500),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 8.h),
Row(
children: [
Icon(Icons.visibility, size: 14.sp, color: Colors.grey),
SizedBox(width: 4.w),
Text(
'${video['views']}次播放',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
],
),
),
],
),
),
),
);
},
),
);
}
标题最多显示两行,超出部分用省略号。播放量前面加了个眼睛图标,让信息更直观。
视频播放的处理
点击视频后弹出一个对话框,提示功能开发中:
void _showVideoDialog(BuildContext context, String title) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(title),
content: const Text('视频功能开发中,敬请期待...'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('确定'),
),
],
),
);
}
}
为什么不直接播放视频? 视频播放涉及到很多东西:视频源、播放器、缓存、全屏等等。这里先用对话框占位。
真实视频播放实现
使用video_player插件
import 'package:video_player/video_player.dart';
class VideoPlayerPage extends StatefulWidget {
final String videoUrl;
const VideoPlayerPage({super.key, required this.videoUrl});
State<VideoPlayerPage> createState() => _VideoPlayerPageState();
}
class _VideoPlayerPageState extends State<VideoPlayerPage> {
late VideoPlayerController _controller;
void initState() {
super.initState();
_controller = VideoPlayerController.network(widget.videoUrl)
..initialize().then((_) {
setState(() {});
_controller.play();
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('视频播放')),
body: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: CircularProgressIndicator(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
使用chewie插件
chewie提供了更完善的播放控制UI:
import 'package:chewie/chewie.dart';
class ChewiePlayerPage extends StatefulWidget {
final String videoUrl;
const ChewiePlayerPage({super.key, required this.videoUrl});
State<ChewiePlayerPage> createState() => _ChewiePlayerPageState();
}
class _ChewiePlayerPageState extends State<ChewiePlayerPage> {
late VideoPlayerController _videoController;
ChewieController? _chewieController;
void initState() {
super.initState();
_initializePlayer();
}
Future<void> _initializePlayer() async {
_videoController = VideoPlayerController.network(widget.videoUrl);
await _videoController.initialize();
_chewieController = ChewieController(
videoPlayerController: _videoController,
autoPlay: true,
looping: false,
aspectRatio: _videoController.value.aspectRatio,
placeholder: Container(color: Colors.black),
autoInitialize: true,
);
setState(() {});
}
void dispose() {
_videoController.dispose();
_chewieController?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('视频播放')),
body: Center(
child: _chewieController != null
? Chewie(controller: _chewieController!)
: CircularProgressIndicator(),
),
);
}
}
视频列表的优化方向
1. 分类筛选
按垃圾类型或难度分类:
final categories = ['全部', '入门', '可回收物', '有害垃圾', '厨余垃圾', '其他垃圾'];
final selectedCategory = '全部'.obs;
Widget _buildCategoryFilter() {
return SizedBox(
height: 40.h,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
return Obx(() => Padding(
padding: EdgeInsets.only(right: 8.w),
child: ChoiceChip(
label: Text(category),
selected: selectedCategory.value == category,
onSelected: (selected) {
if (selected) selectedCategory.value = category;
},
),
));
},
),
);
}
2. 搜索功能
final searchController = TextEditingController();
final filteredVideos = <VideoItem>[].obs;
void _filterVideos(String keyword) {
if (keyword.isEmpty) {
filteredVideos.value = videos;
} else {
filteredVideos.value = videos.where((v) {
return v.title.contains(keyword);
}).toList();
}
}
3. 播放历史
class VideoHistoryService {
static final _history = <String>[].obs;
static void addToHistory(String videoId) {
_history.remove(videoId);
_history.insert(0, videoId);
if (_history.length > 50) {
_history.removeLast();
}
_saveToStorage();
}
static bool isWatched(String videoId) {
return _history.contains(videoId);
}
}
4. 收藏功能
final favoriteVideos = <String>{}.obs;
void toggleFavorite(String videoId) {
if (favoriteVideos.contains(videoId)) {
favoriteVideos.remove(videoId);
} else {
favoriteVideos.add(videoId);
}
_saveFavorites();
}
5. 离线下载
class VideoDownloadService {
static Future<void> downloadVideo(VideoItem video) async {
final dio = Dio();
final savePath = await _getLocalPath(video.id);
await dio.download(
video.videoUrl!,
savePath,
onReceiveProgress: (received, total) {
final progress = received / total;
_updateProgress(video.id, progress);
},
);
}
}
性能优化
1. 缩略图懒加载
CachedNetworkImage(
imageUrl: video.thumbnailUrl!,
placeholder: (context, url) => Container(
color: Colors.grey.shade200,
child: Icon(Icons.play_circle_outline),
),
errorWidget: (context, url, error) => Icon(Icons.error),
)
2. 列表项使用Key
return Card(
key: ValueKey(video.id),
// ...
);
总结
视频教程是个很好的学习形式,特别是对于操作类的内容。本文介绍的实现方案包括:
- 列表布局:卡片式视频列表
- 缩略图设计:播放按钮和时长标签
- 视频播放:video_player和chewie的使用
- 功能扩展:分类、搜索、历史、收藏
虽然现在只是个列表页面,但框架搭好了,后续接入真实视频就很方便了。视频教程作为一种直观的学习方式,能够有效帮助用户掌握垃圾分类的实操技巧。
总结
本文详细介绍了视频教程页面的实现方案,从基础的列表展示到视频播放器的集成都有涉及。通过合理的界面设计,可以为用户提供优质的视频学习体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)