1 ListView

  1. ListView({
  2. ...
  3. //可滚动widget公共参数
  4. Axis scrollDirection = Axis.vertical,
  5. bool reverse = false,
  6. ScrollController controller,
  7. bool primary,
  8. ScrollPhysics physics,
  9. EdgeInsetsGeometry padding,
  10. //ListView各个构造函数的共同参数
  11. double itemExtent,
  12. bool shrinkWrap = false,
  13. bool addAutomaticKeepAlives = true,
  14. bool addRepaintBoundaries = true,
  15. double cacheExtent,
  16. //子widget列表
  17. List<Widget> children = const <Widget>[],
  18. })
  • itemExtent:如果不为null,则会强制children的“长度”为itemExtent的值;这里的“长度”是指滚动方向上子组件的长度,也就是说如果滚动方向是垂直方向,则itemExtent代表子组件的高度;如果滚动方向为水平方向,则itemExtent就代表子组件的宽度。
  • shrinkWrap:该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false。默认情况下,ListView的会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。
  • addAutomaticKeepAlives:该属性表示是否将列表项(子组件)包裹在AutomaticKeepAlive组件中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时它也不会被GC(垃圾回收),它会使用KeepAliveNotification来保存其状态。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。
  • addRepaintBoundaries:该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。当可滚动组件滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效。和addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
  • ScrollDirection: 滚动方向
  • reverse:决定滚动方向是否与阅读方向一致
  • scrollController:控制滚动的位置
  • primary:当内容不足以滚动时,是否支持滚动;对于iOS系统还有一个效果:当用户点击状态栏时是否滑动到顶部。
  • ScrollPhysics:控制用户滚动视图的交互
    • AlwaysScrollableScrollPhysics:列表总是可滚动的。在iOS上会有回弹效果,在android上不会回弹。那么问题来了,如果primary设置为false(内容不足时不滚动),且 physics设置为AlwaysScrollableScrollPhysics,列表是否可以滑动?答案是可以,感兴趣的可以试一下
    • PageScrollPhysics:一般是给PageView控件用的滑动效果。如果listview设置的话在滑动到末尾时会有个比较大的弹起和回弹
    • ClampingScrollPhysics:滚动时没有回弹效果,同android系统的listview效果
    • NeverScrollableScrollPhysics:就算内容超过列表范围也不会滑动
    • BouncingScrollPhysics:不论什么平台都会有回弹效果
    • FixedExtentScrollPhysics:不适用于ListView,原因:需要指定scroller为FixedExtentScrollController,这个scroller只能用于ListWheelScrollViews。

ListView默认构造

适合只有少量的子组件的情况,因为这种方式需要将所有children都提前创建好(这需要做大量工作),而不是等到子widget真正显示的时候再创建

  1. ListView(
  2. shrinkWrap: true,
  3. padding: const EdgeInsets.all(20.0),
  4. children: <Widget>[
  5. const Text('I\'m dedicating every day to you'),
  6. const Text('Domestic life was never quite my style'),
  7. const Text('When you smile, you knock me out, I fall apart'),
  8. const Text('And I thought I was so smart'),
  9. ],
  10. );

ListView.builder

@required IndexedWidgetBuilder itemBuilder 构建列表项

  • itemBuilder:它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
  • itemCount:列表项的数量,如果为null,则为无限列表。
    1. ListView.builder(
    2. itemCount: 100,
    3. itemExtent: 50.0, //强制高度为50.0
    4. itemBuilder: (BuildContext context, int index) {
    5. return ListTile(title: Text("$index"));
    6. }
    7. );

    ListView.custom

    ```dart ListView.custom(childrenDelegate: CustomSliverChildDelegate())

class CustomSliverChildDelegate extends SliverChildDelegate { /// 根据index构造child @override Widget build(BuildContext context, int index) { // KeepAlive将把所有子控件加入到cache,已输入的TextField文字不会因滚动消失 // 仅用于演示 return KeepAlive( keepAlive: true, child: TextField(decoration: InputDecoration(hintText: ‘请输入’))); }

/// 决定提供新的childDelegate时是否需要重新build。在调用此方法前会做类型检查,不同类型时才会调用此方法,所以一般返回true。 @override bool shouldRebuild(SliverChildDelegate oldDelegate) { return true; }

/// 提高children的count,当无法精确知道时返回null。 /// 当 build 返回 null时,它也将需要返回一个非null值 @override int get estimatedChildCount => 100;

/// 预计最大可滑动高度,如果设置的过小会导致部分child不可见,设置报错 @override double estimateMaxScrollOffset(int firstIndex, int lastIndex, double leadingScrollOffset, double trailingScrollOffset) { return 2500; }

/// 完成layout后的回调,可以通过该方法获取即将渲染的视图树包括哪些子控件 @override void didFinishLayout(int firstIndex, int lastIndex) { print(‘didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex’); } }

  1. <a name="DNLEf"></a>
  2. #### ListView.separated
  3. - itemBuilder构建列表项
  4. - separatorBuilder 构建分割组件
  5. ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器
  6. ```dart
  7. class ListView3 extends StatelessWidget {
  8. @override
  9. Widget build(BuildContext context) {
  10. //下划线widget预定义以供复用。
  11. Widget divider1=Divider(color: Colors.blue,);
  12. Widget divider2=Divider(color: Colors.green);
  13. return ListView.separated(
  14. itemCount: 100,
  15. //列表项构造器
  16. itemBuilder: (BuildContext context, int index) {
  17. return ListTile(title: Text("$index"));
  18. },
  19. //分割器构造器
  20. separatorBuilder: (BuildContext context, int index) {
  21. return index%2==0?divider1:divider2;
  22. },
  23. );
  24. }
  25. }
  1. <br />

滚动效果的设置,通过physics属性

  1. NeverScrollablePhysics (不滚动效果)<br /> BouncingScrollPhysics (ios风格的滚动效果)<br /> ClampingScrollPhysics (安卓风格滚动效果)<br /> FixedExtentScrollPhysics (固定范围的滚动效果)<br />应用代码
  1. ListView.separated(
  2. itemCount: 100,
  3. //列表项构造器
  4. itemBuilder: (BuildContext context, int index) {
  5. return ListTile(title: Text("$index"));
  6. },
  7. //分割器构造器
  8. separatorBuilder: (BuildContext context, int index) {
  9. return index%2==0?divider1:divider2;
  10. },
  11. )

2 GridView

[

](https://book.flutterchina.club/chapter6/gridview.html)

  1. GridView({
  2. Axis scrollDirection = Axis.vertical,
  3. bool reverse = false,
  4. ScrollController controller,
  5. bool primary,
  6. ScrollPhysics physics,
  7. bool shrinkWrap = false,
  8. EdgeInsetsGeometry padding,
  9. @required SliverGridDelegate gridDelegate, //控制子widget layout的委托
  10. bool addAutomaticKeepAlives = true,
  11. bool addRepaintBoundaries = true,
  12. double cacheExtent,
  13. List<Widget> children = const <Widget>[],
  14. })

SliverGridDelegate是一个抽象类,定义了GridViewLayout相关接口,子类需要通过实现它们来实现具体的布局算法。
Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,我们可以直接使用SliverGridDelegateWithFixedCrossAxisCount

SliverGridDelegateWithFixedCrossAxisCount

该子类实现了一个横轴为固定数量子元素的layout算法,其构造函数为:

  1. SliverGridDelegateWithFixedCrossAxisCount({
  2. @required double crossAxisCount,
  3. double mainAxisSpacing = 0.0,
  4. double crossAxisSpacing = 0.0,
  5. double childAspectRatio = 1.0,
  6. })
  • crossAxisCount:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度除以crossAxisCount的商。
  • mainAxisSpacing:主轴方向的间距。
  • crossAxisSpacing:横轴方向子元素的间距。
  • childAspectRatio:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后,子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度。

GridView

  1. GridView(
  2. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  3. crossAxisCount: 3, //横轴三个子widget
  4. childAspectRatio: 1.0 //宽高比为1时,子widget
  5. ),
  6. children:<Widget>[
  7. Icon(Icons.ac_unit),
  8. Icon(Icons.airport_shuttle),
  9. Icon(Icons.all_inclusive),
  10. Icon(Icons.beach_access),
  11. Icon(Icons.cake),
  12. Icon(Icons.free_breakfast)
  13. ]
  14. );

GridView.count

  1. GridView.count(
  2. crossAxisCount: 3,
  3. childAspectRatio: 1.0,
  4. children: <Widget>[
  5. Icon(Icons.ac_unit),
  6. Icon(Icons.airport_shuttle),
  7. Icon(Icons.all_inclusive),
  8. Icon(Icons.beach_access),
  9. Icon(Icons.cake),
  10. Icon(Icons.free_breakfast),
  11. ],
  12. );

GridView.extent

  1. GridView.extent(
  2. maxCrossAxisExtent: 120.0,
  3. childAspectRatio: 2.0,
  4. children: <Widget>[
  5. Icon(Icons.ac_unit),
  6. Icon(Icons.airport_shuttle),
  7. Icon(Icons.all_inclusive),
  8. Icon(Icons.beach_access),
  9. Icon(Icons.cake),
  10. Icon(Icons.free_breakfast),
  11. ],
  12. );

GridView.builder

  1. /*
  2. GridView.builder(
  3. ...
  4. @required SliverGridDelegate gridDelegate,
  5. @required IndexedWidgetBuilder itemBuilder,
  6. )
  7. */
  8. class InfiniteGridView extends StatefulWidget {
  9. @override
  10. _InfiniteGridViewState createState() => new _InfiniteGridViewState();
  11. }
  12. class _InfiniteGridViewState extends State<InfiniteGridView> {
  13. List<IconData> _icons = []; //保存Icon数据
  14. @override
  15. void initState() {
  16. // 初始化数据
  17. _retrieveIcons();
  18. }
  19. @override
  20. Widget build(BuildContext context) {
  21. return GridView.builder(
  22. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  23. crossAxisCount: 3, //每行三列
  24. childAspectRatio: 1.0 //显示区域宽高相等
  25. ),
  26. itemCount: _icons.length,
  27. itemBuilder: (context, index) {
  28. //如果显示到最后一个并且Icon总数小于200时继续获取数据
  29. if (index == _icons.length - 1 && _icons.length < 200) {
  30. _retrieveIcons();
  31. }
  32. return Icon(_icons[index]);
  33. }
  34. );
  35. }
  36. //模拟异步获取数据
  37. void _retrieveIcons() {
  38. Future.delayed(Duration(milliseconds: 200)).then((e) {
  39. setState(() {
  40. _icons.addAll([
  41. Icons.ac_unit,
  42. Icons.airport_shuttle,
  43. Icons.all_inclusive,
  44. Icons.beach_access, Icons.cake,
  45. Icons.free_breakfast
  46. ]);
  47. });
  48. });
  49. }
  50. }

3 CustomScrollView

CustomScrollView才可以将多个Sliver”粘”在一起,这些Sliver共用CustomScrollView的Scrollable,所以最终才实现了统一的滑动效果

假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体。如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个”胶水”,把这些彼此独立的可滚动组件”粘”起来,而CustomScrollView的功能就相当于“胶水”。

  1. import 'package:flutter/material.dart';
  2. class CustomScrollViewTestRoute extends StatelessWidget {
  3. @override
  4. Widget build(BuildContext context) {
  5. //因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
  6. //Material Design 默认的样式风格,我们使用Material作为本路由的根。
  7. return Material(
  8. child: CustomScrollView(
  9. slivers: <Widget>[
  10. //AppBar,包含一个导航栏
  11. SliverAppBar(
  12. pinned: true,
  13. expandedHeight: 250.0,
  14. flexibleSpace: FlexibleSpaceBar(
  15. title: const Text('Demo'),
  16. background: Image.asset(
  17. "./images/avatar.png", fit: BoxFit.cover,),
  18. ),
  19. ),
  20. SliverPadding(
  21. padding: const EdgeInsets.all(8.0),
  22. sliver: new SliverGrid( //Grid
  23. gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
  24. crossAxisCount: 2, //Grid按两列显示
  25. mainAxisSpacing: 10.0,
  26. crossAxisSpacing: 10.0,
  27. childAspectRatio: 4.0,
  28. ),
  29. delegate: new SliverChildBuilderDelegate(
  30. (BuildContext context, int index) {
  31. //创建子widget
  32. return new Container(
  33. alignment: Alignment.center,
  34. color: Colors.cyan[100 * (index % 9)],
  35. child: new Text('grid item $index'),
  36. );
  37. },
  38. childCount: 20,
  39. ),
  40. ),
  41. ),
  42. //List
  43. new SliverFixedExtentList(
  44. itemExtent: 50.0,
  45. delegate: new SliverChildBuilderDelegate(
  46. (BuildContext context, int index) {
  47. //创建列表项
  48. return new Container(
  49. alignment: Alignment.center,
  50. color: Colors.lightBlue[100 * (index % 9)],
  51. child: new Text('list item $index'),
  52. );
  53. },
  54. childCount: 50 //50个列表项
  55. ),
  56. ),
  57. ],
  58. ),
  59. );
  60. }
  61. }
  62. /*
  63. * 头部SliverAppBar:SliverAppBar对应AppBar,
  64. 两者不同之处在于SliverAppBar可以集成到CustomScrollView。
  65. SliverAppBar可以结合FlexibleSpaceBar实现Material Design中头部伸缩的模型
  66. * 中间的SliverGrid:它用SliverPadding包裹以给SliverGrid添加补白。
  67. SliverGrid是一个两列,宽高比为4的网格,它有20个子组件。
  68. * 底部SliverFixedExtentList:它是一个所有子元素高度都为50像素的列表。
  69. */
  1. <br />

4 SliverGrid

delegate

SliverChildDelegate 系统提供个两个已经实现好的代理:SliverChildListDelegate/SliverChildBuilderDelegate

gridDelegate

SliverGridDelegate 系统提供个两个已经实现好的代理:SliverGridDelegateWithFixedCrossAxisCount、SliverGridDelegateWithMaxCrossAxisExtent

  1. _mySliverAppBar() {
  2. return SliverAppBar(
  3. title: Text('SliverGrid'),
  4. expandedHeight: 250,
  5. flexibleSpace: FlexibleSpaceBar(
  6. background: Image.network(
  7. ImageUrlConstant.imageUrl1,
  8. fit: BoxFit.cover,
  9. ),
  10. collapseMode: CollapseMode.parallax,
  11. ),
  12. );
  13. }
  14. _mySliverChildBuilderDelegate() {
  15. return SliverChildBuilderDelegate(
  16. (BuildContext context, int index) {
  17. return Container(
  18. height: 50,
  19. color: Colors.primaries[index % 8],
  20. );
  21. },
  22. childCount: 10,
  23. );
  24. }
  25. _mySliverGridDelegateWithFixedCrossAxisCount() {
  26. return SliverGridDelegateWithFixedCrossAxisCount(
  27. crossAxisCount: 2,
  28. mainAxisSpacing: 10,
  29. crossAxisSpacing: 5,
  30. childAspectRatio: 1.5,
  31. );
  32. }
  33. _mySliverGridDelegateWithMaxCrossAxisExtent() {
  34. return SliverGridDelegateWithMaxCrossAxisExtent(
  35. maxCrossAxisExtent: 200,
  36. mainAxisSpacing: 10,
  37. crossAxisSpacing: 5,
  38. childAspectRatio: 1,
  39. );
  40. }
  41. @override
  42. Widget build(BuildContext context) {
  43. return Container(
  44. color: Colors.white,
  45. child: CustomScrollView(
  46. slivers: [
  47. _mySliverAppBar(),
  48. SliverGrid(
  49. delegate: _mySliverChildBuilderDelegate(),
  50. gridDelegate: _mySliverGridDelegateWithFixedCrossAxisCount(),
  51. // gridDelegate: _mySliverGridDelegateWithMaxCrossAxisExtent(),
  52. ),
  53. ],
  54. ),
  55. );
  56. }

5 SliverList

SliverListz原型

  1. const SliverList({
  2. Key key,
  3. @required SliverChildDelegate delegate,
  4. }) : super(key: key, delegate: delegate);

SliverList的使用:

  1. class _SliverListFulState extends State<SliverListFul> {
  2. @override
  3. Widget build(BuildContext context) {
  4. return MaterialApp(
  5. home: Scaffold(
  6. appBar: AppBar(
  7. title: Text("SliverList学习"),
  8. ),
  9. body:CustomScrollView(
  10. slivers: [
  11. SliverList(
  12. delegate: SliverChildBuilderDelegate((content, index) {
  13. return Container(
  14. height: 65,
  15. color: Colors.primaries[index % Colors.primaries.length],
  16. );
  17. }, childCount: 20),
  18. ),
  19. ],
  20. )
  21. ),
  22. );
  23. }
  24. }

6 SliverSafeArea

image.png
顶部有一部分被刘海屏遮挡住了,解决此问题的方法是将SliverGrid包裹在SliverSafeArea中:

  1. CustomScrollView(
  2. slivers: <Widget>[
  3. SliverSafeArea(
  4. sliver: SliverGrid(
  5. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  6. crossAxisCount: 3, crossAxisSpacing: 5, mainAxisSpacing: 3),
  7. delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
  8. return Container(
  9. color: Colors.primaries[index % Colors.primaries.length],
  10. );
  11. }, childCount: 20),
  12. ),
  13. )
  14. ],
  15. )

image.png

7 SingleChildScrollView

里面只能嵌套一个组件,eg : Column,Row

  1. const SingleChildScrollView({
  2. Key key,
  3. //滚动方向,默认是垂直方向
  4. this.scrollDirection = Axis.vertical,
  5. //是否按照阅读方向相反的方向滑动
  6. this.reverse = false,
  7. //内容边距
  8. this.padding,
  9. //是否使用widget树中默认的PrimaryScrollController
  10. bool primary,
  11. //此属性接受一个ScrollPhysics类型的对象,它决定可以滚动如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画,或者滑动到边界时,如何显示。
  12. //默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,对应不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,
  13. //而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显示指定一个固定的ScrollPhysics。
  14. //Flutter SDK包含两个ScrollPhysics的子类。1.ClampingScrollPhysics:Android下微光效果,2.BouncingScrollPhysics:iOS下弹性效果
  15. this.physics,
  16. //此属性接收一个ScrollController对象,ScrollController的主要作用是控制滚动位置和监听滚动事件。
  17. //默认情况下,Widget树中会有一个默认的PrimaryScrollController,如果子树中的可滚动组件没有显示的指定controller,并且primary属性值为true时,可滚动组件会使用这个默认的ScrollController。
  18. //这种机制带来的好处是父组件可以控制子树中可滚动的滚动行为,例:scaffold正是使用这种机制在iOS中实现了点击导航回到顶部的功能。
  19. this.controller,
  20. this.child,
  21. })

使用案例

  1. SingleChildScrollView(
  2. child: new Column(
  3. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  4. crossAxisAlignment: CrossAxisAlignment.center,
  5. children: <Widget>[
  6. new Container(
  7. child: new Column(
  8. children: <Widget>[
  9. new TextField(
  10. decoration: const InputDecoration(
  11. labelText: "Description",
  12. ),
  13. style: Theme.of(context).textTheme.title,
  14. ),
  15. new TextField(
  16. decoration: const InputDecoration(
  17. labelText: "Description",
  18. ),
  19. style: Theme.of(context).textTheme.title,
  20. ),
  21. new TextField(
  22. decoration: const InputDecoration(
  23. labelText: "Description",
  24. ),
  25. style: Theme.of(context).textTheme.title,
  26. ),
  27. ],
  28. )
  29. ),
  30. new Container(
  31. margin: new EdgeInsets.only(bottom: 16.0),
  32. child: new FloatingActionButton(
  33. backgroundColor: new Color(0xFFE57373),
  34. child: new Icon(Icons.check),
  35. onPressed: (){}
  36. ),
  37. )
  38. ],
  39. ),
  40. )