Flutter:实现FutureBuilder下Slider与Smooth Page Indicator联动
问题原因
你用ListView.builder实现横向滑动,但SmoothPageIndicator是基于PageController工作的,ListView和PageController没有绑定关系,滑动时指示器无法同步获取页面切换状态,所以无法联动。
修复步骤
1. 替换ListView为PageView
将账户滑块的ListView.builder改成PageView.builder,并绑定PageController,这样滑动时控制器能实时同步当前页面索引给指示器。
2. 动态设置指示器计数
把硬编码的count:3改成实际账户数据长度snapshot.data!.length,并将SmoothPageIndicator放到FutureBuilder的成功分支里,确保数据加载完成后再渲染指示器。
3. 适配PageView布局
PageView默认让子组件占满父容器宽度,通过设置viewportFraction调整卡片显示比例,同时调整卡片内外边距,保持原布局效果。
修改后的完整代码
import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; //Account Details class AccountDetails { String? address; String? balance; String? coin_name; AccountDetails({ this.address, this.balance, this.coin_name, }); AccountDetails.fromJson(Map<String, dynamic> json) { address = json['address']; balance = json['balance']; coin_name = json['coin_name']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['address'] = address; data['balance'] = balance; data['coin_name'] = coin_name; return data; } } //Transaction Details. class TransactionDetails { String? avatar; String? name; String? date; String? amount; TransactionDetails({ this.avatar, this.name, this.date, this.amount, }); TransactionDetails.fromJson(Map<String, dynamic> json) { avatar = json['avatar']; name = json['name']; date = json['date']; amount = json['amount']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['avatar'] = avatar; data['name'] = name; data['date'] = date; data['amount'] = amount; return data; } } //Transaction Details stuff Future<List<TransactionDetails>> fetchAlbum() async { final response = await http.get(Uri.parse( 'https://brotherlike-navies.000webhostapp.com/people/people.php')); if (response.statusCode == 200) { final List result = json.decode(response.body); return result.map((e) => TransactionDetails.fromJson(e)).toList(); } else { throw Exception('Failed to load data'); } } // Account Details stuff. Future<List<AccountDetails>> fetchAccountDetails() async { final response = await http.get(Uri.parse( 'https://brotherlike-navies.000webhostapp.com/address/addr.php')); if (response.statusCode == 200) { final List result = json.decode(response.body); return result.map((e) => AccountDetails.fromJson(e)).toList(); } else { throw Exception('Failed to load data'); } } class BaseScreen extends StatefulWidget { const BaseScreen({Key? key}) : super(key: key); @override State<BaseScreen> createState() => _BaseScreenState(); } class _BaseScreenState extends State<BaseScreen> { final _controller = PageController(viewportFraction: 0.9); // 控制卡片显示占比 @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text( "My Bank", style: TextStyle( fontFamily: "Inter", color: Colors.white, fontWeight: FontWeight.bold), ), leading: Padding( padding: const EdgeInsets.all(8.0), child: CircleAvatar( backgroundImage: NetworkImage('https://placeimg.com/640/480/people'), ), ), actions: [ IconButton( icon: Icon(Icons.notifications_active_outlined, color: Colors.white, size: 27), onPressed: () {}) ], ), body: SafeArea( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10), Padding( padding: EdgeInsets.all(15), child: Text( "Hello Patrick!", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold), ), ), SizedBox(height: 10), SizedBox( width: double.infinity, height: 200, child: FutureBuilder<List<AccountDetails>>( future: fetchAccountDetails(), builder: (context, snapshot) { if (snapshot.hasData) { return Column( children: [ Expanded( child: PageView.builder( controller: _controller, itemCount: snapshot.data!.length, itemBuilder: (context, index) { return Container( padding: const EdgeInsets.all(15), margin: const EdgeInsets.symmetric(horizontal: 8), width: 319, height: 100, decoration: BoxDecoration( boxShadow: [ BoxShadow( color: const Color.fromARGB(255, 233, 230, 230) .withOpacity(0.8), spreadRadius: 5, blurRadius: 3, offset: const Offset(0, 7), ), ], color: Colors.green, borderRadius: BorderRadius.circular(9)), alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ const Text( 'Account Balance', style: TextStyle( fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( snapshot.data![index].balance.toString(), style: const TextStyle( fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold), ), const SizedBox(width: 5), Text( snapshot.data![index].coin_name.toString(), style: const TextStyle( fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 30), Text( snapshot.data![index].address.toString(), style: const TextStyle( fontSize: 10, color: Colors.white, fontWeight: FontWeight.w600, ), ), ], ), ); }, ), ), SizedBox(height: 15), SmoothPageIndicator( controller: _controller, count: snapshot.data!.length, effect: WormEffect(activeDotColor: Colors.green), ), ], ); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const Center(child: CircularProgressIndicator()); }, ), ), Padding( padding: EdgeInsets.all(15), child: Text( "Recent Transactions", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.green), ), ), Expanded( child: Center( child: FutureBuilder<List<TransactionDetails>>( future: fetchAlbum(), builder: (context, snapshot) { if (snapshot.hasData) { return ListView.builder( shrinkWrap: true, itemCount: snapshot.data!.length, itemBuilder: (context, index) { return ListTile( leading: CircleAvatar( child: Image.network(snapshot .data![index].avatar .toString()), ), title: Text( snapshot.data![index].name.toString()), trailing: Text( snapshot.data![index].amount.toString()), subtitle: Text( snapshot.data![index].date.toString(), style: TextStyle(fontSize: 9), ), ); }); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }))) ]), ), ); } }
关键修改说明
- 将
BaseScreen改为StatefulWidget,更合理管理PageController的生命周期 - 用
PageView.builder替代ListView.builder,绑定控制器实现页面切换同步 - 设置
viewportFraction:0.9让卡片左右留边,配合调整卡片margin保持原布局间距 - 动态设置指示器计数为实际账户数量,确保数据加载完成后渲染正确的指示器
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

