当前位置: 首页 > news >正文

《Flutter篇第二章》MasonryGridView瀑布流列表

学习Flutter ,列表的布局是一个很重要的点,大部分常见的APP 都离不开列表,尤其是瀑布Feed流,其中,很多app 会在列表中嵌套图片、视频和AD 广告,所以今天用MasonryGridView实现了一个列表样式

先看效果:在这里插入图片描述
1、配置

  video_player: ^2.8.0chewie: ^1.5.0flutter_staggered_grid_view: ^0.7.0

2、卡片widget

import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';import '../../modules/main/tabs/home/feed_item.dart';
import '../../modules/main/tabs/home/home_controller.dart';class FeedItemCard extends StatelessWidget {final FeedItem item;const FeedItemCard({super.key, required this.item});Widget build(BuildContext context) {return Card(margin: const EdgeInsets.all(0),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),clipBehavior: Clip.antiAlias,child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [_buildMediaContent(),Padding(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text(item.description,style: const TextStyle(fontSize: 14),maxLines: 2,overflow: TextOverflow.ellipsis,),const SizedBox(height: 8),_buildInteractionRow(),],),),],),);}Widget _buildMediaContent() {final controller = Get.find<HomeController>();return AspectRatio(aspectRatio: item.aspectRatio,child: Stack(children: [if (item.type == 'video')_buildVideoPlayer(controller)elseImage.network(item.url,fit: BoxFit.cover,width: double.infinity,height: double.infinity,),if (item.type == 'video')Positioned(bottom: 8,right: 8,child: Container(padding: const EdgeInsets.all(4),decoration: BoxDecoration(color: Colors.black.withOpacity(0.5),borderRadius: BorderRadius.circular(4),),child: const Icon(Icons.play_arrow,color: Colors.white,size: 16,),),)],),);}Widget _buildVideoPlayer(HomeController ctrl) {return GetBuilder<HomeController>(builder: (context) {final chewieController = ctrl.videoControllers[item.id];if (chewieController == null) {ctrl.initializeVideoPlayer(item.url, item.id);return Container(color: Colors.black,child: const Center(child: CircularProgressIndicator(color: Colors.white)),);}return GestureDetector(onTap: () {if (chewieController.isPlaying) {ctrl.pauseVideo(item.id);} else {ctrl.setActiveVideo(item.id);}},child: Chewie(controller: chewieController),);},);}Widget _buildInteractionRow() {return Row(children: [Obx(() {final controller = Get.find<HomeController>();final itemIndex = controller.feedItems.indexWhere((i) => i.id == item.id);if (itemIndex == -1) return const SizedBox();final currentItem = controller.feedItems[itemIndex];return Row(children: [IconButton(icon: Icon(currentItem.isLiked ? Icons.favorite : Icons.favorite_border,color: currentItem.isLiked ? Colors.red : Colors.grey[700],size: 20,),onPressed: () => controller.toggleLike(item.id),padding: EdgeInsets.zero,constraints: const BoxConstraints(),),const SizedBox(width: 4),Text('${currentItem.likes}',style: const TextStyle(fontSize: 12),),],);}),const SizedBox(width: 16),const Icon(Icons.mode_comment_outlined, size: 20, color: Colors.grey),const SizedBox(width: 16),const Icon(Icons.send, size: 20, color: Colors.grey),],);}
}

3、逻辑加载controller

import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:video_player/video_player.dart';import 'feed_item.dart';class HomeController extends GetxController {var welcomeMessage = ''.obs;final feedItems = <FeedItem>[].obs;final activeVideoId = RxString('');final videoControllers = <String, ChewieController>{};final scrollController = ScrollController();void onInit() {super.onInit();Future.delayed(const Duration(seconds: 1), () {welcomeMessage.value = 'Welcome to Xiaohongshu!';_loadFeedData();});scrollController.addListener(_handleScroll);}void onClose() {for (var controller in videoControllers.values) {controller.dispose();}videoControllers.clear();scrollController.dispose();super.onClose();}void _loadFeedData() {feedItems.assignAll([FeedItem(id: '1',type: 'image',url: 'https://picsum.photos/400/600?random=1',aspectRatio: 400 / 600,description: '夏日海滩度假照片 #旅行 #夏天',likes: 243,),FeedItem(id: '2',type: 'video',url: 'http://vjs.zencdn.net/v/oceans.mp4',aspectRatio: 16 / 9,description: '蝴蝶飞舞的美丽瞬间 #自然 #动物',likes: 512,),FeedItem(id: '3',type: 'image',url: 'https://picsum.photos/400/800?random=2',aspectRatio: 400 / 800,description: '城市夜景 #摄影 #城市',likes: 187,),FeedItem(id: '4',type: 'image',url: 'https://picsum.photos/500/700?random=3',aspectRatio: 500 / 700,description: '美食探店 #美食 #周末',likes: 324,),FeedItem(id: '5',type: 'video',url: 'http://vjs.zencdn.net/v/oceans.mp4',aspectRatio: 16 / 9,description: '周末电影时光 #电影 #休闲',likes: 421,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),FeedItem(id: '6',type: 'image',url: 'https://picsum.photos/450/650?random=4',aspectRatio: 450 / 650,description: '健身打卡 #健身 #健康生活',likes: 278,),]);}void toggleLike(String itemId) {final index = feedItems.indexWhere((item) => item.id == itemId);if (index != -1) {final item = feedItems[index];feedItems[index] = FeedItem(id: item.id,type: item.type,url: item.url,aspectRatio: item.aspectRatio,description: item.description,likes: item.isLiked ? item.likes - 1 : item.likes + 1,isLiked: !item.isLiked,);}}void setActiveVideo(String videoId) {if (activeVideoId.value != videoId) {if (activeVideoId.isNotEmpty) {final currentController = videoControllers[activeVideoId.value];currentController?.pause();}activeVideoId.value = videoId;final controller = videoControllers[videoId];controller?.play();}}void pauseVideo(String videoId) {if (activeVideoId.value == videoId) {activeVideoId.value = '';}final controller = videoControllers[videoId];controller?.pause();}Future<void> initializeVideoPlayer(String videoUrl, String videoId) async {if (videoControllers.containsKey(videoId)) return;final videoController = VideoPlayerController.network(videoUrl);await videoController.initialize();final chewieController = ChewieController(videoPlayerController: videoController,autoPlay: false,looping: true,showControls: false,allowFullScreen: true,materialProgressColors: ChewieProgressColors(playedColor: Colors.red,handleColor: Colors.red,backgroundColor: Colors.grey,bufferedColor: Colors.grey.withOpacity(0.5),));videoControllers[videoId] = chewieController;}void _handleScroll() {for (var item in feedItems) {if (item.type == 'video') {pauseVideo(item.id);}}}
}

5、页面

import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';import '../../../../view/widgets/feed_item_card.dart';
import 'home_controller.dart';class HomePage extends GetView<HomeController> {const HomePage({super.key});Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Obx(() => Text(controller.welcomeMessage.value)),actions: [IconButton(icon: const Icon(Icons.search),onPressed: () {},),IconButton(icon: const Icon(Icons.notifications_none),onPressed: () {},),],),body: Obx(() {if (controller.feedItems.isEmpty) {return const Center(child: CircularProgressIndicator());}return MasonryGridView.count(controller: controller.scrollController,crossAxisCount: 2,itemCount: controller.feedItems.length,itemBuilder: (context, index) {final item = controller.feedItems[index];return FeedItemCard(item: item);},mainAxisSpacing: 8,crossAxisSpacing: 8,padding: const EdgeInsets.all(8),);}),);}
}
http://www.dtcms.com/a/306626.html

相关文章:

  • 机器视觉引导机器人修磨加工系统助力芯片封装
  • 机器人在动态表面上行走的强化学习研究
  • Rust在土木工程中的创新应用
  • Kotlin -> Kotlin Lambda 表达式与 Function 接口的关系
  • EC2 Amazon Linux 快速安装Airbyte (abctl)
  • B 站排名优化:不同领域的差异化实战策略
  • Ubuntu24.04换源方法
  • macOS安装配置Unbound DNS完整指南
  • 地质测绘专业转码容易吗?外业对于转码有帮助吗?
  • WordPress与主流CMS拿Webshell实战
  • NEG指令说明
  • 鸿蒙中相册权限弹窗
  • 单片机电路基础
  • Prompt编写规范指引
  • 一种高维数据可视化的方法:Visualization and Mapping on Arcs (VMA)
  • MySQL的单行函数:
  • uniapp中的$vm
  • Winform 渐变色 调色板
  • 【AI论文】单一领域能否助力其他领域?一项基于数据的、通过强化学习实现多领域推理的研究
  • C#程序员计算器
  • Linux性能分析与内存调试:perf和Valgrind高级使用指南
  • 数据结构(7)单链表算法题OVA
  • Tlias 案例-整体布局(前端)
  • docker镜像加速及国内镜像源
  • cocos打包web - ios设备息屏及前后台切换音频播放问题
  • 美国人工智能行动计划:洞察 AI 发展新路径​​
  • 怎样在 Vue 中定义全局方法?
  • 快速删除Word和WPS文字中的空白行
  • LLM—— 基于 MCP 协议(SSE 模式)的工具调用实践
  • PYTHON从入门到实践-17通过网络API获取数据与可视化