在Flutter中下拉刷新

73
我的仪表板代码看起来像这样, 在getReport方法中,我正在进行get请求,我在代码中添加了RefreshIndicator,当在容器内向下拉时应该进行刷新,那里我调用了我的getData()函数。但是我没有得到更新后的内容,我在下面添加了我的代码,请让我知道是否有任何错误。
下面是我的dashboard.dart:
class Window extends StatefulWidget {
  @override
  _WindowState createState() => _WindowState();
}

class _WindowState extends State<Window> {
  Future reportList;    
  @override
  void initState() {
    super.initState();
    reportList = getReport();
  }    

  Future<void> getReport() async {
    http.Response response =
        await http.get(reportsListURL, headers: {"token": "$token"});
    switch (response.statusCode) {
      case 200: 
        String reportList = response.body;
        var collection = json.decode(reportList);
        return collection;

      case 403:
          break;

      case 401:
        return null;

      default:
        return 1;
    }
  }

  getRefreshScaffold() {
    return Center(
      child: RaisedButton(
        onPressed: () {
          setState(() {
            reportList = getReport();
          });
        },
        child: Text('Refresh, Network issues.'),
      ),
    );
  }

  getDashBody(var data) {
    double maxHeight = MediaQuery.of(context).size.height;
    return Column(
      children: <Widget>[
        Container(
          height: maxHeight - 800,
        ),
        Container(
          margin: new EdgeInsets.all(0.0),
          height: maxHeight - 188,
          child: new Center(
          child: new RefreshIndicator(          //here I am adding the RefreshIndicator
          onRefresh:getReport,                  //and calling the getReport() which hits the get api
          child: createList(context, data),
          ),),
        ),
      ],
    );
  }

  Widget createList(BuildContext context, var data) {
    Widget _listView = ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, count) {
        return createData(context, count, data);
      },
    );
    return _listView;
  }

  createData(BuildContext context, int count, var data) {
    var metrics = data["statistic_cards"].map<Widget>((cardInfo) {
      var cardColor = getColorFromHexString(cardInfo["color"]);
      if (cardInfo["progress_bar"] != null && cardInfo["progress_bar"]) {
        return buildRadialProgressBar(
          context: context,
          progressPercent: cardInfo["percentage"],
          color: cardColor,
          count: cardInfo["value"],
          title: cardInfo["title"],
        );
      } else {
        return buildSubscriberTile(context, cardInfo, cardColor);
      }
    }).toList();

    var rowMetrics = new List<Widget>();
    for (int i = 0; i < metrics.length; i += 2) {
      if (i + 2 < metrics.length)
        rowMetrics.add(Row(children: metrics.sublist(i, i + 2)));
      else
        rowMetrics.add(Row(children: [metrics[metrics.length - 1], Spacer()]));
    }
    return SingleChildScrollView(
      child: LimitedBox(
        //  maxHeight: MediaQuery.of(context).size.height / 1.30,
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: rowMetrics,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: reportList,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.waiting:
          case ConnectionState.active:
            return Center(
              child: CircularProgressIndicator(),
            );
          case ConnectionState.done:
            var data = snapshot.data;
            if (snapshot.hasData && !snapshot.hasError) {
              return getDashBody(data);
            } else if (data == null) {
              return Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    Text("Timeout! Log back in to continue"),
                    Padding(
                      padding: EdgeInsets.all(25.0),
                    ),
                    RaisedButton(
                      onPressed: () {
                        setState(() {
                          token = null;
                        });
                        Navigator.of(context).pushReplacement(
                          CupertinoPageRoute(
                              builder: (BuildContext context) => LoginPage()),
                        );
                      },
                      child: Text('Login Again!'),
                    ),
                  ],
                ),
              );
            } else {
              getRefreshScaffold();
            }
        }
      },
    );
  }
}

1
将你的ListView用RefreshIndicator包装起来,它就能正常工作了。 - Murat Aslan
我没有完全理解你要做什么。 - mark
看一下@Baker的回答:https://dev59.com/y1MH5IYBdhLWcg3w1z9_#62206884 - Mamrezo
6个回答

124

基本示例

下面是一个 StatefulWidget 的 State 类,其中:

  • 一个 ListView 包含在 RefreshIndicator
  • numbersList 状态变量是其数据源
  • onRefresh 调用 _pullRefresh 函数以更新数据和 ListView
  • _pullRefresh 是一个异步函数,返回空值 (a Future<void>)
  • _pullRefresh 的长时间运行的数据请求完成后,在 setState() 调用中更新 numbersList 成员 / 状态变量以重建 ListView 来显示新的数据
import 'package:flutter/material.dart';
import 'dart:math';

class PullRefreshPage extends StatefulWidget {
  const PullRefreshPage();

  @override
  State<PullRefreshPage> createState() => _PullRefreshPageState();
}

class _PullRefreshPageState extends State<PullRefreshPage> {
  List<String> numbersList = NumberGenerator().numbers;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: _pullRefresh,
        child: ListView.builder(
          itemCount: numbersList.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(numbersList[index]),
            );
          },),
      ),
    );
  }

  Future<void> _pullRefresh() async {
    List<String> freshNumbers = await NumberGenerator().slowNumbers();
    setState(() {
      numbersList = freshNumbers;
    });
    // why use freshNumbers var? https://dev59.com/fFcP5IYBdhLWcg3wd5qm#52992836
  }
}

class NumberGenerator {
  Future<List<String>> slowNumbers() async {
    return Future.delayed(const Duration(milliseconds: 1000), () => numbers,);
  }

  List<String> get numbers => List.generate(5, (index) => number);


  String get number => Random().nextInt(99999).toString();
}

注意事项

  • 如果您的异步onRefresh函数完成非常快,您可能希望在它之后添加await Future.delayed(Duration(seconds: 2)); ,以使用户体验更加愉悦。
  • 这将给用户足够的时间来完成滑动/下拉手势,并使刷新指示器渲染/动画/旋转,表明数据已被获取。

FutureBuilder 示例

这是使用FutureBuilder的上面State<PullRefreshPage>类的另一个版本,当从数据库或HTTP源获取数据时,这是很常见的:

class _PullRefreshPageState extends State<PullRefreshPage> {
  late Future<List<String>> futureNumbersList;

  @override
  void initState() {
    super.initState();
    futureNumbersList = NumberGenerator().slowNumbers();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<List<String>>(
        future: futureNumbersList,
        builder: (context, snapshot) {
          return RefreshIndicator(
            child: _listView(snapshot),
            onRefresh: _pullRefresh,
          );
        },
      ),
    );
  }

  Widget _listView(AsyncSnapshot snapshot) {
    if (snapshot.hasData) {
      return ListView.builder(
        itemCount: snapshot.data.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(snapshot.data[index]),
          );
        },);
    }
    else {
      return Center(
        child: Text('Loading data...'),
      );
    }
  }

  Future<void> _pullRefresh() async {
    List<String> freshNumbers = await NumberGenerator().slowNumbers();
    setState(() {
      futureNumbersList = Future.value(freshNumbers);
    });
  }
}

注释

  • slowNumbers()函数与上面的基本示例中相同,但数据包装在Future.value()中,因为FutureBuilder需要一个Future,但setState()不应该等待异步数据。
  • 根据Rémi、Collin和其他Dart/Flutter大神的说法,在长时间运行的异步数据获取函数完成,在setState()内更新Stateful Widget成员变量(FutureBuilder示例中的futureNumbersList和基本示例中的numbersList)是一个好习惯。
  • 如果尝试将setState设置为async,将会导致异常
  • setState之外更新成员变量,并使用空的setState闭包可能导致手动操作或代码分析警告。

1
е®ҢзҫҺзҡ„жҸҸиҝ°е’Ңжң¬ең°Flutterи§ЈеҶіж–№жЎҲпјҲж— йңҖдҫқиө–пјүпјҒ - Mamrezo
完美解决方案。谢谢。 - mark
1
很棒的答案。特别好的是你还使用了FutureBuilder来解释解决方案。 - Loolooii
FutureBuilder这个例子非常方便和有帮助!非常感谢。 - Muralidharan Deenathayalan
FutureBuilder这个例子非常方便和有帮助!非常感谢。 - undefined

15

关于未来事项不确定,但对于刷新指示器,您必须返回空值,因此请使用以下代码:

RefreshIndicator(
                onRefresh: () async  {
                  await getData().then((lA) {
                    if (lA is Future) {
                      setState(() {
                        reportList = lA;
                      });
                      return;
                    } else {
                      setState(() {
                       //error
                      });
                      return;
                    }
                  });

                  return;
                },

试一下这个,然后告诉我!

编辑:

好的,在你的刷新方法中尝试这个。

          setState(() {
            reportList = getReport();  
          });
          return reportList;

如果你需要未来,也许可以尝试删除我写的 await async 参数。 - Cristian Bregant
onRefresh: () async { await getReport().then((lA) { if (lA is Future) { setState(() { reportList = lA; }); return; } else { setState(() { //错误 }); return; } }); return; }, child: createDashList(context, data), ),), ) , - mark
我已经在 getDashBody 中编写了这个,但它对我没有起作用。 - mark

4

3
在不可滚动的列表视图中,RefreshIndicator 无法正常工作,因此您需要使用 Stack 将您的小部件包装起来以实现下拉刷新。
RefreshIndicator(
  onRefresh: () {
  // Refresh Functionality 
  },
  child: Stack(
            children: [
              ListView(
                padding: EdgeInsets.zero,
                shrinkWrap: true,
                children: [
                  SizedBox(
                    height: MediaQuery.of(context).size.height,
                  )
                ],
              ),
              // Your Widget 
            ],
          );
        ),

3

试试这个:

onRefresh: () {
  setState(() {});
}}

应该使用onRefresh: getReport替代

reportList字段是一个只返回一次的Future。因此,如果再次调用getReport,它不会有任何变化。实际上,更正确的做法应该是使用StreamStreamBuilder而不是FutureFutureBuilder。但对于这段代码来说,使用onRefresh: getReport是最短的解决方案。


当我修改后,出现了这个错误信息:“函数表达式不能被命名。请尝试删除名称或将函数表达式移动到函数声明语句中。” - mark
最初只有getReport(),当我添加了RefreshIndicator后,出现了错误,说该方法应该是future类型的,所以我将gerReport重命名为=> Future<void> getReport。 - mark
抱歉。我编辑了我的回答。 - Andrii Turkovskyi

0

我正在开发一个庞大的项目,其中包含CustomScrollView、NestedScrollView、ListView等。我尝试了以上所有答案,所有答案都使用来自Flutter SDK的RefreshIndicator。但它并不能完全适用于我的应用程序,因为我还有水平滚动视图。因此,为了实现它,我不得不在几乎每个屏幕上使用NestedScrollView。然后我了解到liquid_pull_to_refresh,将其应用于顶部小部件,WOLAAH!如果您需要为每个屏幕使用单独的逻辑,则在每个屏幕的顶部使用它,但在我的情况下,我正在刷新整个项目的数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接