朋也的博客 » 首页 » 文章

Flutter ListView 用法,图文列表的实现,加载静态数据,请求接口渲染动态数据

作者:朋也
日期:2019-07-16
类别:flutter学习笔记 


版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

先上图

静态数据

首先创建项目,不多说

图文列表在 Flutter 里有个组件专门渲染图文列表的 ListTile 代码如下

import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
            appBar: AppBar(title: Text('Flutter Demo')), body: MyHomeWidget()));
  }
}

class MyHomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题1'),
            subtitle: Text('我是副标题1'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题2'),
            subtitle: Text('我是副标题2'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题3'),
            subtitle: Text('我是副标题3'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题4'),
            subtitle: Text('我是副标题4'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题5'),
            subtitle: Text('我是副标题5'),
            trailing: Icon(Icons.chevron_right)),
      ],
    );
  }
}

渲染出来长下面这个样

可以看到它没有分割线,那怎么加上分割线呢?

添加分割线

使用 ListTile.divideTiles() 方法即可,如下

class MyHomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: ListTile.divideTiles(context: context, tiles: [
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题1'),
            subtitle: Text('我是副标题1'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题2'),
            subtitle: Text('我是副标题2'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题3'),
            subtitle: Text('我是副标题3'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题4'),
            subtitle: Text('我是副标题4'),
            trailing: Icon(Icons.chevron_right)),
        ListTile(
            leading: Image.network(
                "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"),
            title: Text('我是标题5'),
            subtitle: Text('我是副标题5'),
            trailing: Icon(Icons.chevron_right)),
      ]).toList(),
    );
  }
}

接文原链: https://atjiu.github.io/2019/07/16/flutter-listview/


另一种添加分割线的方法,使用 ListView.separated() 方法,用法如下

class MyHomeWidget extends StatelessWidget {
  List data = new List();
  MyHomeWidget() {
    for (var i = 0; i < 5; i++) {
      data.add({
        "title": "我是标题$i",
        "subTitle": "我是副标题$i",
        "imgUrl": "https://avatars3.githubusercontent.com/u/6915570?s=460&v=4"
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemCount: this.data.length,
      itemBuilder: (context, index) {
        var _data = this.data[index];
        return ListTile(
            leading: Image.network(_data["imgUrl"]),
            title: Text(_data["title"]),
            subtitle: Text(_data["subTitle"]),
            trailing: Icon(Icons.chevron_right));
      },
      separatorBuilder: (context, index) {
        return Divider();
      },
    );
  }
}

效果图

动态数据

上面第二种方法其实就算是一种动态数据了,不过开发app时列表中的数据一般都是通过请求接口来获取的

当涉及到请求接口,就是耗时操作,app的ui会事先渲染一个没有数据的界面,当接口请求的数据返回时,再通知 ListView 重新渲染,这时就要用到**有状态**的组件

往上翻一下,可以看到,前面写的组件都是 无状态 的,也就是继承的组件都是 StatelessWidget

如何使用有状态的组件呢?其实使用 flutter create xxx初始化的项目里的那个计数器就是个有状态的组件,因为它涉及到了动态的数据了,既然默认初始化的组件就是有状态的组件,那直接拷贝过来用即可

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  MyHomeWidget createState() => MyHomeWidget();
}

class MyHomeWidget extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return null;
  }
}

可以看到自定义的类 MyHomePage 继承的是 StatefulWidget这个类,这个类里是个抽象类,里面有个抽象方法 createState() 要实现,写法很固定

下面又定义了一个类通过继承 State 类来实现业务逻辑,当语法接口的数据返回了,再调用父类的 setState() 方法,将数据源更新,组件就会自动的渲染了

这个 setState() 方法,如果会用 react.js 这样的前端框架,就很容易明白了,如果不明白,就把它当成 android 里的 listview.notifyDataSetChanged() 方法就可以了

这篇博客重点是记录 ListView 的用法,网络请求就不多说了,后面再介绍

我这用的接口是 https://cnodejs.org 的接口,因为要请求接口,并要将数据转成json, 这里要引入两个依赖,具体代码如下

import 'dart:io';
import 'dart:convert';
class MyHomeWidget extends State<MyHomePage> {
  List data = new List();
  var baseUrl = "https://cnodejs.org/api/v1";
  MyHomeWidget() {
    HttpClient client = new HttpClient();
    client
        .getUrl(Uri.parse("${this.baseUrl}/topics?mdrender=false"))
        .then((HttpClientRequest request) {
      // Optionally set up headers...
      // Optionally write to the request object...
      // Then call close.
      return request.close();
    }).then((HttpClientResponse response) async {
      // Process the response.
      // 通过 utf8 转码,jsonDecode 将数据转成json格式
      var json = await response.transform(utf8.decoder).join();
      setState(() {
        data.addAll(jsonDecode(json)["data"]);
      });
    }).catchError((onError) {
      print(onError.toString());
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemCount: this.data.length,
      itemBuilder: (context, index) {
        var _data = this.data[index];
        return ListTile(
            leading: Image.network(_data["author"]["avatar_url"]),
            title: Text(_data["title"]),
            subtitle: Text(_data["author"]["loginname"] +
                " created at " +
                _data["create_at"]),
            trailing: Icon(Icons.chevron_right));
      },
      separatorBuilder: (context, index) {
        return Divider();
      },
    );
  }
}

参考