Flutter 16: 图解 ListView 异步加载数据与 Loading 等待

简介: 0 基础学习 Flutter,第十六步:ListView 日常小问题,包括异步数据加载及 Loading 等待过程~

      小菜前两天再学 ListView 时,整理了一下在列表中展示多种不同 item 样式,今天继续深入学习异步请求数据并加载新闻列表以及初始进入页面的 loading 等小知识点。暂时还没有学习下拉刷新与上划加载更多。

一. 异步请求数据 async + wait

      小菜在前一篇关于网络请求小博客中整理过基本的异步使用方法;小菜在学习中发现有两个小地方需要注意一下:

  1. 使用 StatefulWidget 时,一定一定不要忘记 setState(() {})
  2. 小菜准备在刚进入页面时,开启异步请求数据,可以在 initState() 中进行操作,如下:
@override
void initState() {
    getNewsData();
}

二. json 数据解析

      请求到数据之后必然得需要 json 解析,首先需要引入 import 'dart:convert' show json; 之后,小菜主要是使用 response.body 中数据进行处理,json.decode(response.body); 将 json 转为标准的 key-value 格式;最让小菜头疼的是实体类转换,实体类的定义一定要全面且字段格式正确,不然解析出问题不容易定位。(请谅解:小菜的测试 url 无法公布)

getNewsData() async {
  await http
      .get('https://...?sid=xkycs&cid=${cid}&rowNumber=${rowNumber}')
      .then((response) {
    if (response.statusCode == 200) {
      var jsonRes = json.decode(response.body);
      newsListBean = NewsListBean(jsonRes);
      setState(() {
        for (int i = 0; i < newsListBean.list.length; i++) {
          print("==77==${newsListBean.list[i].title}");
          dataItems.add(newsListBean.list[i]);
        }
      });
    }
  });
}

      小菜单独为实体类区分为一个新的 .dart 文件,需要注意的是,若实体类中有列表,一定要注意判空,如下:

class NewsListBean {
  List<ListBean> list;
  int rowNumber;
  bool success;
  String msg;

  NewsListBean(jsonRes) {
    rowNumber = jsonRes['rowNumber'];
    success = jsonRes['success'];
    msg = jsonRes['msg'];
    list = [];
    if (jsonRes['list'] != null) {
      for (var dataItem in jsonRes['list']) {
        list.add(new ListBean(dataItem));
      }
    }
  }
}

class ListBean {
  int fileID;
  String title;
  int version;
  String abstractX;
  String publishTime;
  String realPublishTime;
  int articleType;
  String pic3;
  String pic2;
  String pic1;
  int bigPic;
  String tag;
  String contentUrl;
  String videoImgUrl;

  ListBean(jsonRes) {
    fileID = jsonRes['fileID'];
    title = jsonRes['title'];
    version = jsonRes['version'];
    abstractX = jsonRes['abstract'];
    publishTime = jsonRes['publishTime'];
    realPublishTime = jsonRes['realPublishTime'];
    articleType = jsonRes['articleType'];
    pic3 = jsonRes['pic3'];
    pic2 = jsonRes['pic2'];
    pic1 = jsonRes['pic1'];
    bigPic = jsonRes['bigPic'];
    tag = jsonRes['tag'];
    contentUrl = jsonRes['contentUrl'];
    videoImgUrl = jsonRes['videoImgUrl'];
  }
}

三. 列表加载数据

      小菜每次写 item 时都会想到 Flutter 中一切都是 Widget 的重要性,小菜建议很多公共的或重复的 Widget 完全可以提取成统一的 Widget,即方便管理也会大幅度减少代码量。

Widget buildListData(BuildContext context, ListBean listBean) {
  Widget itemWidget;
  if (listBean != null) {
    switch (listBean.articleType) {
      case 1:
        itemWidget = new Card(
          child: new Container(
            child: new Column(
              children: <Widget>[
                new Row(
                  children: <Widget>[
                    new Expanded(
                      child: new Container(
                        padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
                        child: new Image.network( listBean.pic1, fit: BoxFit.cover, ),
                        height: 100.0,
                      ),
                      flex: 1,
                    ),
                    new Expanded(
                      child: new Container(
                        padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
                        child: new Image.network( listBean.pic2, fit: BoxFit.cover, ),
                        height: 100.0,
                      ),
                      flex: 1,
                    ),
                    new Expanded(
                      child: new Container(
                        padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
                        child: new Image.network( listBean.pic3, fit: BoxFit.cover, ),
                        height: 100.0,
                      ),
                      flex: 1,
                    ),
                  ],
                ),
                new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 6.0), child: botRow(listBean), ),
              ],
            ),
          ),
        );
        break;
      case 2:
        itemWidget = new Card(
          child: new Column(
            children: <Widget>[
              new Row(
                children: <Widget>[
                  new Expanded(
                    child: new Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: new Text(listBean.title), ),
                        new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: new Text( listBean.abstractX, style: new TextStyle(fontSize: 14.0), ), ),
                        new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: botRow(listBean), ),
                      ],
                    ),
                    flex: 2,
                  ),
                  new Expanded(
                    child: new Container(
                      padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 6.0),
                      child: new Image.network( listBean.pic1, fit: BoxFit.cover, ),
                      height: 100.0,
                    ),
                    flex: 1,
                  ),
                ],
              ),
            ],
          ),
        );
        break;
      default:
        Widget absWi;
        if (listBean.abstractX == null || listBean.abstractX.length == 0) {
          absWi = new Container( width: 0.0, height: 0.0, );
        } else {
          absWi = new Padding( padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),child: new Text( listBean.abstractX, style: new TextStyle(fontSize: 14.0), ), );
        }
        itemWidget = new Card(
          child: new Padding(
            padding: new EdgeInsets.all(10.0),
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                new Text( listBean.title, style: new TextStyle(fontSize: 17.0), ),
                absWi,
                new Padding( padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0), child: botRow(listBean), ),
              ],
            ),
          ),
        );
        break;
    }
    return itemWidget;
  }
}

// 底部时间和音视频显隐性
Widget botRow(ListBean listBean) {
  Widget videoWi;
  if (listBean.videoImgUrl == null || listBean.videoImgUrl.length == 0) {
    videoWi = new Container( width: 0.0, height: 0.0, );
  } else {
    videoWi = new Padding(
      padding: new EdgeInsets.fromLTRB(0.0, 0.0, 6.0, 0.0),
      child: new Row(
        children: <Widget>[
          new Padding(
            padding: new EdgeInsets.fromLTRB(0.0, 2.0, 6.0, 0.0),
            child: new Center( child: new Icon( Icons.queue_music, size: 13.0, color: Colors.blueAccent, ), ),
          ),
          new Text( '音频', style: new TextStyle(fontSize: 13.0), ),
        ],
      ),
    );
  }
  return new Padding(
    padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),
    child: new Row(
      children: <Widget>[
        videoWi,
        new Padding(
          padding: new EdgeInsets.fromLTRB(0.0, 0.0, 6.0, 0.0),
          child: new Icon( Icons.access_time, size: 13.0, color: Colors.blueAccent,),
        ),
        new Text( listBean.publishTime, style: new TextStyle(fontSize: 13.0),),
      ],
    ),
  );
}

      小菜处理成在没有加载出列表数据之前添加一个 loading 提醒,如下:

Widget childWidget() {
  Widget childWidget;
  if (dataItems != null && dataItems.length != 0) {
    childWidget = new Padding(
      padding: EdgeInsets.all(6.0),
      child: new ListView.builder(
        itemCount: dataItems.length,
        itemBuilder: (context, item) {
          return buildListData(context, dataItems[item]);
        },
      ),
    );
  } else {
    childWidget = new Stack(
      children: <Widget>[
        new Padding(
          padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 35.0),
          child: new Center(
            child: SpinKitFadingCircle(
              color: Colors.blueAccent,
              size: 30.0,
            ),
          ),
        ),
        new Padding(
          padding: new EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),
          child: new Center(
            child: new Text('正在加载中,莫着急哦~'),
          ),
        ),
      ],
    );
  }
  return childWidget;
}

四. loading 提醒

      小菜在加载数据之后发现,网络状况不佳或数据量大时都应有 loading 提醒,尽量给用户一个良好的体验。
      小菜偷了个懒,借用一个三方库 flutter_spinkit,这个 loading 库集成简单而且效果多样,基本包含日常中常见的样式。

集成步骤:

  1. pubspec.yaml 中添加 flutter_spinkit: "^2.1.0"
  2. 在相应的 .dart 文件中添加引用 import 'package:flutter_spinkit/flutter_spinkit.dart';
  3. 添加需要展示的样式:SpinKit + Wave() 方式,同时与官网的使用有点区别,官网中用 width 和 height 来设置宽高,但是小菜在测试过程中,源码中提供了 size 方法,一个属性即可。

new Column(
  children: <Widget>[
    new SpinKitRotatingPlain(color: Colors.blueAccent, size: 30.0,),
    new SpinKitRotatingCircle(color: Colors.blueAccent, size: 30.0,),
    new SpinKitDoubleBounce(color: Colors.blueAccent, size: 30.0,),
    new SpinKitRing(color: Colors.blueAccent, size: 30.0,),
    new SpinKitWave(color: Colors.blueAccent, size: 30.0,),
    new SpinKitWanderingCubes(color: Colors.blueAccent, size: 30.0,),
    new SpinKitFadingCube(color: Colors.blueAccent, size: 30.0,),
    new SpinKitFadingFour(color: Colors.blueAccent, size: 30.0,),
    new SpinKitPulse(color: Colors.blueAccent, size: 30.0,),
    new SpinKitChasingDots(color: Colors.blueAccent, size: 30.0,),
    new SpinKitHourGlass(color: Colors.blueAccent, size: 30.0,),
    new SpinKitSpinningCircle(color: Colors.blueAccent, size: 30.0,),
  ],
)


      小菜刚接触 Flutter 时间不长,还有很多不清楚和不理解的地方,如果又不对的地方还希望多多指出。以下是小菜公众号,欢迎闲来吐槽~
公众号

目录
相关文章
|
6月前
Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套
Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套 在 Flutter 中,ListView 和 PageView 是两个常用的控件,它们可以用于滑动展示大量内容的场景,且支持各种嵌套方式,本文将介绍其中的一些花式嵌套方式。
262 0
在 Flutter 中如何使用 ChangeNotifierProvider 实现数据共享?
在 Flutter 中如何使用 ChangeNotifierProvider 实现数据共享?
|
15天前
深入理解Flutter鸿蒙next版本 中的Widget继承:使用extends获取数据与父类约束
本文详细介绍了Flutter中如何通过继承其他Widget来创建自定义组件。首先解释了Widget继承的基本概念,包括StatelessWidget和StatefulWidget的区别。接着通过具体示例展示了如何继承StatelessWidget和StatefulWidget,并在子类中访问父类的build方法和状态。最后,结合多个自定义Widget展示了如何在实际应用中灵活使用继承和组合来构建复杂的UI。
66 8
|
15天前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
93 1
|
15天前
|
JSON Dart 数据格式
<大厂实战场景> ~ flutter&鸿蒙next处理后端返回来的数据的转义问题
在 Flutter 应用开发中,处理后端返回的数据是常见任务,尤其涉及转义字符时。本文详细探讨了如何使用 Dart 的 `dart:convert` 库解析包含转义字符的 JSON 数据,并提供了示例代码和常见问题的解决方案,帮助开发者有效处理数据转义问题。
110 0
|
3月前
|
前端开发
Flutter快速实现自定义折线图,支持数据改变过渡动画
Flutter快速实现自定义折线图,支持数据改变过渡动画
91 4
Flutter快速实现自定义折线图,支持数据改变过渡动画
|
3月前
|
UED
Flutter之ListView实现自动滑动到底部
Flutter之ListView实现自动滑动到底部
205 1
|
3月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
60 0
|
3月前
|
开发者 容器 Java
Azure云之旅:JSF应用的神秘部署指南,揭开云原生的新篇章!
【8月更文挑战第31天】本文探讨了如何在Azure上部署JavaServer Faces (JSF) 应用,充分发挥其界面构建能力和云平台优势,实现高效安全的Web应用。Azure提供的多种服务如App Service、Kubernetes Service (AKS) 和DevOps简化了部署流程,并支持应用全生命周期管理。文章详细介绍了使用Azure Spring Cloud和App Service部署JSF应用的具体步骤,帮助开发者更好地利用Azure的强大功能。无论是在微服务架构下还是传统环境中,Azure都能为JSF应用提供全面支持,助力开发者拓展技术视野与实践机会。
18 0
|
3月前
|
开发框架 API 开发者
Flutter表单控件深度解析:从基本构建到高级自定义,全方位打造既美观又实用的移动端数据输入体验,让应用交互更上一层楼
【8月更文挑战第31天】在构建美观且功能强大的移动应用时,表单是不可或缺的部分。Flutter 作为热门的跨平台开发框架,提供了丰富的表单控件和 API,使开发者能轻松创建高质量表单。本文通过问题解答形式,深入解读 Flutter 表单控件,并通过具体示例代码展示如何构建优秀的移动应用表单。涵盖创建基本表单、处理表单提交、自定义控件样式、焦点管理和异步验证等内容,适合各水平开发者学习和参考。
80 0