You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:30:45