首页 > 解决方案 > 为什么在 ListView Flutter 中滚动时会多次调用 FutureBuilder?

问题描述

我有一个带有子 FutureBuilder 和 ListView.Builder 的 ListView。在 FutureBuilder 中,我有 ListView。现在的问题是,当我滚动 ListView 父级时,为什么 FutureBuilder 会再次重建?

您可以在这里查看我如何重现此问题https://youtu.be/OKjiMOSJmYA

import 'package:flutter/material.dart';

class DebugScreen extends StatefulWidget {
  @override
  _DebugScreenState createState() => _DebugScreenState();
}

class _DebugScreenState extends State<DebugScreen> {
  List<int> numbers = [1, 2, 3, 4, 5, 6, 7];
  var futureData;
  var futureBuilderHelloWorld;

  Future<List<int>> getFutureData() async {
    print("getFutureData");
    var numbers = [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
      17,
      18,
      19,
      20,
      21,
      22,
      23,
      24,
      25,
      26,
      27,
      28,
      29,
      30,
    ];
    await Future.delayed(Duration(seconds: 3));
    return await numbers;
  }

  @override
  void initState() {
    futureData = getFutureData();
    futureBuilderHelloWorld = FutureBuilder(
      future: futureData,
      builder: (context, AsyncSnapshot<List<int>> snapshot) {
        print("future builder"); // why this is called when scrolling
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.waiting:
            return Center(
              child: CircularProgressIndicator(),
            );
          default:
            if (snapshot.hasError) {
              return Text("Error: " + snapshot.error.toString());
            } else {
              var numbers = snapshot.data;
              return Container(
                height: 190.0,
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: numbers.length,
                  itemBuilder: (context, index) {
                    return Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text("Hello world ${numbers[index]}"),
                    );
                  },
                ),
              );
            }
        }
      },
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Debug Mode"),
      ),
      body: ListView(
        children: <Widget>[
          futureBuilderHelloWorld,
          ListView(
            shrinkWrap: true,
            physics: ClampingScrollPhysics(),
            children: List.generate(100, (index) {
              return Text("Flutter $index");
            }).toList(),
          )
        ],
      ),
    );
  }
}

现在在控制台中就像这样

I/flutter (18634): future builder
I/flutter (18634): future builder
I/flutter (18634): future builder
I/flutter (18634): future builder

标签: flutter

解决方案


在文档中指出,如果您将来发生更改,构建方法将始终被调用两次:

If the old future has already completed successfully with data as above, changing configuration to a new future results in snapshot pairs of the form:

new AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')
new AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')
In general, the latter will be produced only when the new future is non-null, and the former only when the old future is non-null.

查看文档:https ://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

您的代码应该通过保持呼叫计数器和仅在第二次呼叫中更新列表来适应这一点。


推荐阅读