RefreshIndicator+FutureBuilder 实现下拉刷新上滑加载数据

3,336 阅读3分钟

Flutter 中可以通过 RefreshIndicator 和 FutrueBuilder 实现异步网络请求,对于下拉刷新和上滑加载数据也可以很方便的实现。这里以知乎日报 API 为例演示,仅供学习交流。

说明

在 Flutter 中,异步 UI 更新可以通过 FutureBuilder 和 StreamBuilder 这两个组件来实现,对于网络请求,通常使用 Futrue + FutureBuilder 组合来完成。而对于下拉刷新功能,Flutter 也单独提供了一个 Widget —— RefreshIndicator 来实现,这个组件有一个 onRefresh 方法,这里回调异步请求数据的操作就能实现下拉刷新的功能。

  const RefreshIndicator({
   Key key,
   @required this.child,
   this.displacement = 40.0,
   @required this.onRefresh,
   this.color,
   this.backgroundColor,
   this.notificationPredicate = defaultScrollNotificationPredicate,
   this.semanticsLabel,
   this.semanticsValue,
 }) : assert(child != null),
      assert(onRefresh != null),
      assert(notificationPredicate != null),
      super(key: key);

对于上滑加载数据,通常通过 ListView 或者其他可滚动组件的 ScrollController 来完成,通过 ScrollController 可以判断滚动列表是否滚动到底部,如果是,就调用上滑加载的功能获取数据即可。

效果:

实现

一、网络请求

这里以知乎日报 API 为例说明,两个 api 如下


///最新消息
const String LAST_NEWS = "https://news-at.zhihu.com/api/4/news/latest";

///历史消息
const String HISTORY_NEWS = "https://news-at.zhihu.com/api/4/news/before/";

使用 Dio 进行网络请求:

Response     response = await dio.get(LAST_NEWS);

这里返回的数据是 Map 类型的,可以根据 key 直接拿到我们想要的结果即可。 如,获取消息中的日期字段:

   currentDate = response.data["date"].toString();

二、FutureBuilder + ListView 展示数据

FutureBuilder 需要结合 Future 使用,先定义一个 Future,异步网络请求。

  ///先定义一个 Future
  Future getDataFuture;
  ...
 
 @override
 void initState() {
   super.initState();
   getDataFuture = getItemNews();
 } 
 
   ///获取最新消息
 Future<List<dynamic> > getItemNews() async{
   items.clear();

   print("开始获取最新消息.");
   response = await dio.get(LAST_NEWS);
   currentDate = response.data["date"].toString();

   if(response.data["stories"] != null){
     items.addAll(response.data["stories"]);
   }

   return items;
 }

 

FutureBuilder + ListView 展示数据

 FutureBuilder<List<dynamic>>(
         ///指定刷新数据的 Future
         future: getDataFuture,
         builder: (context,AsyncSnapshot<List<dynamic>> async){
           ///正在请求时的视图
           if (async.connectionState == ConnectionState.active || async.connectionState == ConnectionState.waiting) {
             return Center(
               child: Text("loading..."),
             );
           }
           ///发生错误时的视图
           if (async.connectionState == ConnectionState.done) {
             if (async.hasError) {
               return Center(
                 child: Text("error"),
               );
             } else if (async.hasData && async.data != null && async.data.length > 0) {

               List resultList = async.data;

               return  ListView.builder(
                   controller: _scrollController,
                   itemCount: resultList.length + 1,
                   itemExtent: 100.0,
                   itemBuilder: (BuildContext context, int index) {
                     return index < async.data.length  ?  Container(
                       child:  Card(
                         child: Row(
                           children: <Widget>[
                             Expanded(
                               child: Container(
                                 child: Text(resultList[index]["title"].toString(),style: TextStyle(fontSize:20),),
                                 padding: EdgeInsets.symmetric(horizontal: 10),
                               ),
                               flex: 2,
                             ),

                             Expanded(
                               child: Container(
                                 child:Image.network(resultList[index]["images"][0].toString()),
                                 padding: EdgeInsets.all(5),
                               ),
                               flex: 1,
                             ),

                           ],
                         ),
                       ),
                       height: 50,
                     ): Center(
                       child: isShowProgress? CircularProgressIndicator(
                         strokeWidth: 2.0,
                       ):Container(),
                     );

                   }
               );



             }else{
               return Center(
                 child: Text("error"),
               );
             }
           }
           return Center(
             child: Text("error"),
           );
         },
       ),

三、RefreshIndicator + ScrollController 实现下拉刷新和上滑加载

上面的代码已经实现了异步加载和显示数据的功能,对于下拉刷新,只要在 FutureBuilder 外面嵌套 RefreshIndicator 并指定 onRefresh 即可。

 RefreshIndicator(
       onRefresh: getItemNews,
       child:   FutureBuilder<List<dynamic>>(
         future: getDataFuture,
         ...
         )
   )      

上滑加载数据通过 ScrollController 来实现:

  ScrollController _scrollController = ScrollController();

 @override
 void initState() {
   super.initState();
   getDataFuture = getItemNews();
   _scrollController.addListener(() {
     if (_scrollController.position.pixels ==
         _scrollController.position.maxScrollExtent) {
       print("get more");
       _getMore(currentDate);
     }
   });

 }
 
 
   _getMore(String date) async{
   if(date == "")
     return;

   setState(() {
     isShowProgress = true;
   });

   Map<String, dynamic> historyMap ;
   response = await dio.get(HISTORY_NEWS + date);
   historyMap = response.data;

   if(historyMap != null && historyMap.length > 0){
     List<dynamic> stories = historyMap["stories"];
     if(stories != null && stories.length > 0){
       currentDate = historyMap["date"].toString();
     }


     if(response.data["stories"] != null){
       items.addAll(response.data["stories"]);
     }


   }

   setState(() {
     isShowProgress = false;
   });

 }


在 LisbView 里面,我指定了数据的长度为返回的数据长度 + 1,目的就是为了现在最下面的 CircularProgressIndicator 对话框,而上面的变量 isShowProgress 可控制是否显示这个组件的。

github

最后

欢迎关注「Flutter 编程开发」微信公众号 。