Flutter中PageView与GetX结合的索引及数据展示异常问题
解决Flutter中Stateful Widget结合GetX与带动画PageView的问题
问题背景
我习惯在Stateless Widget中通过GetX控制器结合ListView展示基于Model类的数据,流程为:创建Model类→GetX控制器实现数据获取→GetView构建ListView。但尝试实现带动画的PageView时,无法与自己的Model类结合,且不喜欢使用Stateful Widget。目前在Stateful Widget中实现时遇到以下问题:
- 前景图片无法随PageView索引变化更新
- 出现
IntegerDivisionByZeroException异常 - 不清楚Stateful Widget结合GetX控制器与Model类实现带动画PageView的正确方式
以下是我的代码基础及尝试过程:
1. FoodModel类实现
class FoodModel { String? foodId; String? foodNameEn; String? foodNameAr; String? kcal; String? fats; String? carbs; String? protein; String? createTime; String? foodTypeId; String? foodImage; FoodModel( {this.foodId, this.foodNameEn, this.foodNameAr, this.kcal, this.fats, this.carbs, this.protein, this.createTime, this.foodTypeId, this.foodImage}); FoodModel.fromJson(Map<String, dynamic> json) { foodId = json['food_id']; foodNameEn = json['food_name_en']; foodNameAr = json['food_name_ar']; kcal = json['kcal']; fats = json['fats']; carbs = json['carbs']; protein = json['protein']; createTime = json['create_time']; foodTypeId = json['food_type_id']; foodImage = json['food_image']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['food_id'] = foodId; data['food_name_en'] = foodNameEn; data['food_name_ar'] = foodNameAr; data['kcal'] = kcal; data['fats'] = fats; data['carbs'] = carbs; data['protein'] = protein; data['create_time'] = createTime; data['food_type_id'] = foodTypeId; data['food_image'] = foodImage; return data; } }
2. 尝试的PageView实现(更新后)
我已注入GetX控制器,但前景图片固定在初始索引,背景图片正常变化,且出现除零异常:
// 注入控制器 FoodControllerImp controller = Get.put(FoodControllerImp()); // 获取索引方式 int get _currentIndex=>_currentOffest.round() % controller.food.length; // 背景图片调用 BgImage( currentIndex: _currentIndex, food: FoodModel.fromJson(controller.food[_currentIndex]), pageOffset: _currentOffest, ), // 前景图片调用(固定初始索引) late final FoodModel foodlist = FoodModel.fromJson(controller.food[_currentIndex]); Image.network( "${ApiLinks.foodImg}/${foodlist.foodImage}", fit: BoxFit.scaleDown, ),
解决方案
1. 修复前景图片不更新问题
问题出在foodlist的初始化方式:late final只会初始化一次,导致数据固定在初始索引。需每次直接从控制器列表中获取对应索引的Model数据:
- 移除
late final FoodModel foodlist = ...代码 - 在需要使用数据的位置,直接通过索引获取:
// 在PageView.builder的itemBuilder中 final FoodModel food = FoodModel.fromJson(controller.food[index]); Image.network( "${ApiLinks.foodImg}/${food.foodImage}", fit: BoxFit.scaleDown, )
- 同时将PageView的
itemCount设置为controller.food.length(而非固定的3),确保遍历所有数据。
2. 解决IntegerDivisionByZeroException异常
异常原因是数据未加载完成时,controller.food.length为0,导致取模运算除以0。需在计算索引前判断数据状态:
int get _currentIndex { // 数据为空时返回0,避免除零 if (controller.food.isEmpty) return 0; return _currentOffest.round() % controller.food.length; }
同时在页面构建时,先监听控制器的加载状态,确保数据就绪后再渲染PageView:
@override Widget build(BuildContext context) { return GetBuilder<FoodControllerImp>( builder: (controller) { if (controller.statusRequest == StatusRequest.loading) { return const Center(child: CircularProgressIndicator()); } else if (controller.food.isEmpty) { return const Center(child: Text("暂无数据")); } return AnimatedBuilder( animation: _pageController, builder: (context, child) { return stackBuild(); }, ); }, ); }
3. Stateful Widget结合GetX与Model类的正确实现方式
核心思路:用GetX管理数据状态,Stateful Widget仅负责PageView的动画与滚动状态,两者解耦。完整步骤如下:
步骤1:优化控制器数据存储
修改FoodControllerImp的getData方法,直接将接口返回的Map转换为FoodModel列表存储,避免页面中重复调用fromJson:
@override void getData() async { statusRequest = StatusRequest.loading; update(); var response = await categoriesData.getData(); statusRequest = handlingData(response); if(StatusRequest.success == statusRequest){ if(response['status'] == "success") { // 直接转换为Model列表 food = (response['exercise_category'] as List) .map((e) => FoodModel.fromJson(e)) .toList(); } else { statusRequest = StatusRequest.taskFailure; } } update(); }
步骤2:完整的PageView实现代码
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../data/model/food_model.dart'; import '../../../controller/food_controller.dart'; import '../../../links/api_links.dart'; class FoodSliderBuilder extends StatefulWidget { const FoodSliderBuilder({super.key}); @override State<FoodSliderBuilder> createState() => _FoodSliderBuilderState(); } class _FoodSliderBuilderState extends State<FoodSliderBuilder> { late PageController _pageController; // 获取全局注入的控制器(若未全局注入,可在initState中用Get.put()) final FoodControllerImp controller = Get.find<FoodControllerImp>(); double get _currentOffset { bool inited = _pageController.hasClients && _pageController.position.hasContentDimensions; return inited ? _pageController.page! : _pageController.initialPage * 1.0; } int get _currentIndex { if (controller.food.isEmpty) return 0; return _currentOffset.round() % controller.food.length; } @override void initState() { _pageController = PageController(initialPage: 0); super.initState(); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GetBuilder<FoodControllerImp>( builder: (controller) { if (controller.statusRequest == StatusRequest.loading) { return const Center(child: CircularProgressIndicator()); } else if (controller.food.isEmpty) { return const Center(child: Text("暂无食物数据")); } return AnimatedBuilder( animation: _pageController, builder: (context, child) { return stackBuild(); }, ); }, ); } Stack stackBuild() { final FoodModel _currFood = controller.food[_currentIndex]; return Stack( alignment: Alignment.center, children: [ Positioned( top: -Get.width * 0.7, child: BgImage( currentIndex: _currentIndex, food: _currFood, pageOffset: _currentOffset, ), ), SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Text( _currFood.foodNameEn!, textAlign: TextAlign.center, style: const TextStyle(fontSize: 25, fontWeight: FontWeight.bold), ), ], ), ), Center( child: SizedBox( height: Get.width * (450 / 375.0), child: PageView.builder( controller: _pageController, itemCount: controller.food.length, itemBuilder: (context, index) { double _value = 0.0; double vp = 1; double scale = max(vp, (_currentOffset - index).abs()); if (_pageController.position.haveDimensions) { _value = index.toDouble() - (_pageController.page ?? 0); _value = (_value * 0.7).clamp(-1, 1); } final FoodModel food = controller.food[index]; return Transform.rotate( angle: pi * _value, child: Padding( padding: EdgeInsets.only(bottom: 200 - scale * 5), child: Image.network( "${ApiLinks.foodImg}/${food.foodImage}", fit: BoxFit.scaleDown, ), ), ); }, ), ), ), ], ); } } class BgImage extends StatelessWidget { const BgImage({super.key, required this.currentIndex, required this.food, required this.pageOffset}); final int currentIndex; final FoodModel food; final double pageOffset; @override Widget build(BuildContext context) { double _value = 0.0; _value = (pageOffset - currentIndex + 1).abs(); return Opacity( opacity: 0.6, child: Transform.rotate( angle: pi + _value + pi / 180, child: SizedBox( height: Get.height, width: Get.width * (450 / 375.0), child: Image.network( "${ApiLinks.foodImg}/${food.foodImage}", fit: BoxFit.fitWidth, ), ), ), ); } }
内容的提问来源于stack exchange,提问作者Toxicless
相关产品推荐
相关产品推荐

