Flutter ListView & GridView

| ListView

  1. 默认构造函数 数据较少时,可以直接使用如下方式(一次加载所有的组件,没有“懒加载”,和使用 SingleChildScrollView+Column 的方式没有本质的区别):
ListView(
  children: <Widget>[
    _ListItem(title: '1',),
    _ListItem(title: '2',),
    _ListItem(title: '3',),
    _ListItem(title: '4',),
    _ListItem(title: '5',),
    _ListItem(title: '6',),
  ],
)
// _ListItem 定义如下:
class _ListItem extends StatelessWidget {
  final String title;
  const _ListItem({Key key, this.title}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Container(
        height: 45,
        alignment: Alignment.center,
        child: Text('$title'),
      ),
    );
  }
}
  1. ListView.builder 当有大量数据时,使用动态创建列表的方式(当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的 ListView 是支持基于 Sliver 的懒加载模型的):
ListView.builder(
  // 列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项
  itemBuilder: (BuildContext context, int index) {
    return _ListItem(
      title: '$index',
    );
  },
  itemExtent: 50, // 每一个Item的高度
  itemCount: 30, // 个数
  scrollDirection: Axis.horizontal, // 滚动方向,默认是垂直方向,可以设置为水平方向。
  reverse: false, //是否反转滚动方向,比如当前滚动方向是垂直方向,reverse : true,滚动方向为从上倒下,reverse:false,滚动方向为从下倒上。
  shrinkWrap: true,
)

在 ListView 中,指定 itemExtent 比让子组件自己决定自身长度会更高效,这是因为指定 itemExtent 后,滚动系统可以提前知道列表的长度,而无需每次构建子组件时都去再计算一下,尤其是在滚动位置频繁变化时(滚动系统需要频繁去计算列表高度)

shrinkWrap:该属性表示是否根据子组件的总长度来设置 ListView 的长度,默认值为 false 。默认情况下,ListView 的会在滚动方向尽可能多的占用空间。当 ListView 在一个无边界(滚动方向上)的容器中时,shrinkWrap 必须为 true。

ScrollController 是 ListView 组件的控制器,通过 ScrollController 可以获取当前滚动的位置,也可以滚动到指定的位置。用法如下:

class _ListViewDemoState extends State<ListViewDemo> {
  ScrollController _controller;

  @override
  void initState() {
    _controller = ScrollController()
      ..addListener(() {
        print('${_controller.position}');
      });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _controller,
      reverse: false,
      itemBuilder: (BuildContext context, int index) {
        return _ListItem(
          title: '$index',
        );
      },
      itemCount: 30,
      itemExtent: 50,
    );
  }
}

// 指定跳转到指定位置:
@override
Widget build(BuildContext context) {
  return Column(
    children: <Widget>[
      Container(
        child: RaisedButton(
          child: Text('滚动到指定位置'),
          onPressed: () {
            _controller.animateTo(200,
                duration: Duration(milliseconds: 300), curve: Curves.linear);
          },
        ),
      ),
      Expanded(
        child: ListView.builder(
          controller: _controller,
          reverse: false,
          itemBuilder: (BuildContext context, int index) {
            return _ListItem(
              title: '$index',
            );
          },
          itemCount: 30,
          itemExtent: 50,
        ),
      )
    ],
  );
}

physics 参数表示当滚动到顶部或者底部时滚动的物理特性。比如设置为不可滚动:

系统提供的 ScrollPhysics 有:

  • AlwaysScrollableScrollPhysics:总是可以滑动
  • NeverScrollableScrollPhysics:禁止滚动
  • BouncingScrollPhysics :内容超过一屏 上拉有回弹效果
  • ClampingScrollPhysics :包裹内容 不会有回弹
ListView(
    physics: NeverScrollableScrollPhysics(),
  ···
)
  1. ListView.separated 可以在生成的列表项之间添加一个分割组件,它比 ListView.builder 多了一个 separatorBuilder 参数,该参数是一个分割组件生成器
ListView.separated(
  itemBuilder: (BuildContext context, int index) {
    return Container(
      height: 45,
      alignment: Alignment.center,
      child: Text('$index'),
    );
  },
  separatorBuilder: (BuildContext context, int index){
    return Divider();
  },
  itemCount: 30,
)

列表上方添加标题:

@override
Widget build(BuildContext context) {
  return Column(children: <Widget>[
    ListTile(title:Text("商品列表")),
    Expanded( // 解决ListView高度边界无法确定
      child: ListView.builder(itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("$index"));
      }),
    ),
  ]);
}

和 ListTile 配合使用

ListTile 支持的参数非常多,常用是 onTap、leading、trailing、title、subtitle

一个 ListTile 也由 leading、trailing 和 title + subtitle 组成

return ListTile(
  leading: Container(
          child: Image.network(
            IMAGE_SRC,
            fit: BoxFit.cover,
          ),
          color: Colors.grey,
          width: 60,
          height: 60),
  trailing: Icon(Icons.chevron_right, color: Colors.pink),
  title: Text(TITLE),
  subtitle: Text(
    SUB_TITLE,
    maxLines: 1,
    overflow: TextOverflow.ellipsis,
  ),
);

通过函数生成 List

方法 _getNewsList() 最终返回 List,其实非常简单就是使用 list.map() 返回一个 Widget 列表,需要注意的是 map 方法返回的是 Iterable,并非 List,需要 toList()

  List<Widget> _getNewsList() {
    return newsList.news.map((item) {
      return ListItem(
        title: '${item['title']}',
        subTitle: '${item['time']}',
        cover: '${item['imgurl']}',
      );
    }).toList();
  }

| GridView

基本用法:

GridView(
  scrollDirection: Axis.horizontal, // 同listview
  reverse: true, // // 同listview
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3, //横轴三个子 widget
    childAspectRatio: 1.0 //宽高比为 1 时,子 widget
  ),
  children:<Widget>[
    Icon(Icons.ac_unit),
    Icon(Icons.airport_shuttle),
    Icon(Icons.all_inclusive),
    Icon(Icons.beach_access),
    Icon(Icons.cake),
    Icon(Icons.free_breakfast)
  ]
);

gridDelegate 类型是 SliverGridDelegate,它的作用是控制 GridView 子组件如何排列(layout),有 2 个选择:

  • SliverGridDelegateWithFixedCrossAxisCount:交叉轴方向上固定数量,对于垂直方向的 GridView 来说交叉轴方向指的是水平方向。
  • SliverGridDelegateWithMaxCrossAxisExtent:交叉轴方向上尽量大,比如水平方上有 500 空间,指定此值为 150,那么可以放 3 个,剩余一些空间,此时 GridView 将会缩小每一个 Item,放置 4 个。

SliverGridDelegateWithFixedCrossAxisCount 有属性介绍如下:

  • crossAxisCount:交叉轴方向上个数。
  • mainAxisSpacing:主轴方向上 2 行之间的间隔。
  • crossAxisSpacing:交叉轴方向上之间的间隔。
  • childAspectRatio:子控件宽高比。

controller 和 physics 属性用法同 ListView 中一样

GridView 提供了一些快速构建的方法

  1. GridView.builder
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  // 构建子控件
  itemBuilder: (context, index) {
    return Container(
      height: 80,
      color: Colors.primaries[index % Colors.primaries.length],
    );
  },
  itemCount: 50, // 指定数据个数
)
  1. GridView.custom
GridView.custom(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  childrenDelegate: SliverChildBuilderDelegate((context, index) {
    return Container(
        height: 80,
        color: Colors.primaries[index % Colors.primaries.length]);
  }, childCount: 50),
)
  1. GridView.count
body: new GridView.count(
  crossAxisCount: 4, // 交叉轴方向上个数
  childAspectRatio: 0.7, // 子控件宽高比。
  mainAxisSpacing: 5, // 主轴方向上2行之间的间隔。
  crossAxisSpacing: 36, // 交叉轴方向上之间的间隔。
  children: List.generate(100, (index){
    return new Center(
      child: new Text(
        'Item ${index}',
        style: Theme.of(context).textTheme.headline,
      ),
    );
  })
)
  1. GridView.extent
GridView.extent(
  maxCrossAxisExtent: 100,
  children: List.generate(50, (i) {
    return Container(
      height: 80,
      color: Colors.primaries[i % Colors.primaries.length],
    );
  }),
)