ListView


创建方式:

  1. 直接使用默认构造函数创建:适合数据量较小的列表,一次性创建所有子控件。

    Widget build(BuildContext context) {  return ListView(
        children: List.generate(100, (index) {      return ListTile(
            leading: Icon(Icons.people),
            trailing: Icon(Icons.delete),
            title: Text("联系人"),
            subtitle: Text("联系电话:188888888"),
          );
        }),
      );
    }复制代码
  2. 使用命名构造函数ListView.builder创建。适用于数据量较大或者无限数据量的情况,因为不会一次性构建所有的子widget,widget只有在需要展示的时候才会被创建。

    Widget build(BuildContext context) {  return ListView.builder(
        itemCount: 100,//指定子widget数量,若不指定,则是无限数量itemBuilder: (BuildContext context, int index){return ListTile(
          title: Text("ListView.builder"),
          subtitle: Text("Index: $index"),
        );
      });
    }复制代码
  3. 使用ListView.separated创建。跟ListView.builder类似,增加了separatorBuilder,用于显示分割线。适用于有固定数目widget的列表显示。

    Widget build(BuildContext context) {return ListView.separated(
        itemBuilder: (BuildContext context, int index){return ListTile(
              title: Text("ListView.separated"),
              subtitle: Text("Index: $index"),
            );
          },
          separatorBuilder: (BuildContext context, int index){return Divider(
              color: Colors.red,
              indent: 16,
              endIndent: 16,
            );
          },
          itemCount: 100
      );
    }复制代码
  4. ListView.custom

官方文档对四种构造器的说明:

There are four options for constructing a ListView:

  1. The default constructor takes an explicit List of children. This constructor is appropriate for list views with a small number of children because constructing the List requires doing work for every child that could possibly be displayed in the list view instead of just those children that are actually visible.

  2. The ListView.builder constructor takes an IndexedWidgetBuilder, which builds the children on demand. This constructor is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible.

  3. The ListView.separated constructor takes two IndexedWidgetBuilders: itemBuilder builds child items on demand, and separatorBuilder similarly builds separator children which appear in between the child items. This constructor is appropriate for list views with a fixed number of children.

  4. The ListView.custom constructor takes a SliverChildDelegate, which provides the ability to customize additional aspects of the child model. For example, a SliverChildDelegate can control the algorithm used to estimate the size of children that are not actually visible.

GridView

文档链接

  1. 默认构造器GridView():跟ListView的默认构造方法类似,一次性构建所有的子控件。

    Widget build(BuildContext context) {  return GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8),
        children: List.generate(100, (index){      return Container(
            color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
          );
        }),
      );
    }复制代码
  2. GridView.count和GridView.extent

    这两个构造方法的使用跟上面的默认构造方法类似,可以看作是上面默认构造方法的简便用法。这两个构造方法都默认传递了gridDelegate对应的值:

  • GridView.count:SliverGridDelegateWithFixedCrossAxisCount
  • GridView.extent:SliverGridDelegateWithMaxCrossAxisExtent

GridView.builder:需要显示某个子控件的时候,才会创建子控件。

GridView.custom:

CustomScrollView

为什么需要使用CustomScrollView呢?

在实现相对复杂的滚动布局的时候,我们需要使用CustomScrollView控件,比如在一个滚动布局中需要以列表形式(ListView的展示方式)展示一部分内容,还需要以九宫格形式(GridView的展示方式)展示一部分内容,这种情况下,单纯地使用ListView或者GridView是很难实现的。这时候就需要CustomScrollView了。

相对于ListView跟GridView的使用,CustomScrollView的使用相对要复杂一些。这三种滚动控件,直接或间接继承自Scrollview。了解相关的继承结构,也许能更好地使用CustomScrollView。

通过查看源码,可以得到如下的继承关系:Flutter的滚动控件_Flutter

Scrollview中的buildSlivers方法,是抽象方法,需要由子类来实现,而BoxScrollView跟CustomScrollView也实现了该方法。

BoxScrollView的buildSlivers方法中,调用了buildChildLayout方法,该方法又是一个抽象方法,需要由子类来实现。如下:Flutter的滚动控件_Flutter_02

作为BoxScrollView的子类,ListView跟GridView实现了buildChildLayout方法。

ListView中的实现如下:Flutter的滚动控件_滚动控件_03GridView中的实现如下:Flutter的滚动控件_滚动控件_04可以看到,ListView的buildSlivers方法最终返回了包含SliverFixedExtentList或者SliverList的list,而GridView的buildSlivers方法最终返回的是包含SliverGrid的list。

以上就是对着三种控件继承结构及相关的方法调用的分析。

对于CustomScrollView来说,我们需要在它的slivers属性中,放置多个类型的Sliver来实现较为复杂的滚动布局。

常用的Sliver:

  • SliverList:ListView的样式。
  • SliverFixedExtentList:ListView的样式,可以设置滚动的高度。
  • SliverGrid:GridView的样式。
  • SliverPadding: 设置Sliver的内边距,因为可能需要单独设置Sliver内边距。
  • SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView。
  • SliverSafeArea:设置内容在安全区内显示,避免内容被遮挡。

    与SafeArea的区别:SafeArea不能放在CustomScrollView的slivers中,但是可以包裹CustomScrollView。在滚动控件向上滚动的时候,SafeArea不能一起向上滑动,也就是不能穿透到手机的状态栏底下(z轴),而SliverSafeArea可以。

示例:

import 'package:flutter/material.dart';import 'package:flutter/rendering.dart';import 'dart:math';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override
  Widget build(BuildContext context) {return MaterialApp(
      home: YQHomePage(),
    );
  }
}class YQHomePage extends StatelessWidget {  @override
  Widget build(BuildContext context) {return Scaffold(      // appBar: AppBar(  //   title: Text("CustomScrollView"),  // ),  body: YQHomeBody(),
    );
  }
}class YQHomeBody extends StatefulWidget {  @override
  _YQHomeBodyState createState() => _YQHomeBodyState();
}class _YQHomeBodyState extends State<YQHomeBody> {

  SliverGrid getSliverGrid(){return SliverGrid(
        delegate:SliverChildBuilderDelegate((BuildContext context, int index) {          return Container(
            color: _randomColor(),
          );
        }, childCount: 6),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 8.0,
          crossAxisSpacing: 8.0,
          childAspectRatio: 2)
    );
  }

  SliverList getSliverList(){return SliverList(
      delegate:
      SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(
          title: Text("hello, $index"),
        );
      }, childCount: 20),
    );
  }  @override
  Widget build(BuildContext context) {
    debugPaintSizeEnabled = false;return CustomScrollView(
      slivers: [
        SliverAppBar(
          title: Text("SliverAppBar"),
          backgroundColor: Colors.red,
          pinned: true,//是否允许导航栏一起滚动),
        SliverPadding(
          sliver: getSliverGrid(),
          padding: EdgeInsets.all(8.0)
        ),
        getSliverList()
      ],
    );
  }
}

Color _randomColor() {  return Color.fromARGB(      255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
}复制代码

滚动监听

两种监听方式:

  1. ScrollController,在可滚动控件中设置controller属性即可
  • 可以设置默认偏移的offset
  • 监听滚动,通过controller实例还可以获取到滚动的位置
NotificationListener(本质是一个widget),将需要被监听的滚动控件使用该控件包裹并设置NotificationListener的onNotification属性即可
  • 可以监听到开始滚动和结束滚动