Flutter学习记录——9.列表滚动

1.CustomScrollView Widget

CustomScrollView 是一个可以自己通过 Flutter 里的 sliver 来组装滚动 Widget 的一个控件,里面可以放置任何我们需要滚动的 Widget,也是相对来说最常使用的一个滚动组件。

我们看下 CustomScrollView 的继承关系:

CustomScrollView -> ScrollView -> StatelessWidget

CustomScrollView 是一个无状态组件,继承自 ScrollView,也扩展了 ScrollView 的功能。

CustomScrollView 最大的特点就是内部的组装的滚动组件都是 Sliver 特性的,也就是必须是 Sliver 可滚动块的 Widget 才可以,如:CustomScrollView 内部可以放置 SliverList、SliverFixedExtentList、SliverGrid、SliverPadding、SliverAppBar、SliverToBoxAdapter 等组件。

CustomScrollView 非常强大,如我们可以把 ListView 和 GridView 组装在一起,也可以拼装其他的 Sliver Widget,实现更复杂的效果。

我们看下 CustomScrollView 的构造方法:

const CustomScrollView({
    Key key,
    // 滚动方向
    Axis scrollDirection = Axis.vertical,
    // 是否反向显示
    bool reverse = false,
    // 滚动控制对象
    ScrollController controller,
    // 是否是与父级关联的主滚动视图
    bool primary,
    // 滚动视图应如何响应用户输入
    ScrollPhysics physics,
    // 是否根据正在查看的内容确定滚动视图的范围
    bool shrinkWrap = false,
    Key center,
    double anchor = 0.0,
    // 缓存区域
    double cacheExtent,
    // 内部sliver组件
    this.slivers = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  })

接下来我们通过一个实例来看下如何使用 CustomScrollView:

@override
  Widget build(BuildContext context) {
    return Material(
      child: CustomScrollView(
        // slivers里面放置sliver滚动块组件
        slivers: <Widget>[
          // 放置一个顶部的标题栏
          SliverAppBar(
            // 是否固定在顶部
            pinned: true,
            // 展开高度
            expandedHeight: 250.0,
            // 可展开区域,通常是一个FlexibleSpaceBar
            flexibleSpace: FlexibleSpaceBar(
              title: const Text('CustomScrollView'),
              background: Image.asset("assets/image_appbar.jpg",fit: BoxFit.cover,),
            ),
          ),
          // 放置一个SliverGrid Widget
          SliverGrid(
            // 设置Grid属性:
            // SliverGridDelegateWithMaxCrossAxisExtent:
            // 按照设置最大扩展宽度计算item个数
            // SliverGridDelegateWithFixedCrossAxisCount:
            // 可以固定设置每行item个数
            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
              // item最大宽度
              maxCrossAxisExtent: 200.0,
              // 主轴item间隔
              mainAxisSpacing: 10.0,
              // 交叉轴item间隔
              crossAxisSpacing: 10.0,
              // item宽高比
              childAspectRatio: 4.0,
            ),
            // 设置item的布局及属性
            // SliverChildListDelegate:适用于有固定数量的item的List
            // SliverChildBuilderDelegate:适用于不固定数量的item的List
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  alignment: Alignment.center,
                  color: Colors.teal[100 * (index % 9)],
                  child: Text('grid item $index'),
                );
              },
              // 20个item数量
              childCount: 20,
            ),
          ),
          // 指定item高度的List
          SliverFixedExtentList(
            // item固定高度
            itemExtent: 50.0,
            // 设置item布局和属性
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  alignment: Alignment.center,
                  color: Colors.lightBlue[100 * (index % 9)],
                  child: Text('list item $index'),
                );
              },
              childCount: 20,
            ),
          ),
        ],
      ),
    );
  }

运行效果如图:

CustomScrollView

接下来再给一个稍微复杂点的实例:

ScrollController _scrollController =
    ScrollController(initialScrollOffset: 5, keepScrollOffset: true);

Widget customScrollview1() {
  _scrollController.addListener(() {
    ///滚动监听
  });
  return CustomScrollView(
    shrinkWrap: false,
    primary: false,
    // 回弹效果
    physics: BouncingScrollPhysics(),
    scrollDirection: Axis.vertical,
    controller: _scrollController,
    slivers: <Widget>[
      ///SliverGrid用法
      SliverGrid(
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return Container(
              alignment: Alignment.center,
              color: Colors.teal[100 * (index % 9)],
              child: Text(
                'grid item $index',
                style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
              ),
            );
          },
          childCount: 20,
        ),

        ///设置Grid属性:
        ///SliverGridDelegateWithMaxCrossAxisExtent:
        ///按照设置最大扩展宽度计算item个数
        ///SliverGridDelegateWithFixedCrossAxisCount:
        ///可以固定设置每行item个数
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 5,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,

          ///item高度缩放比例,默认为1;小于1表示放大,大于1表示缩小
          childAspectRatio: 1,
        ),
        //  SliverGridDelegateWithMaxCrossAxisExtent(
        //   ///item最大宽度
        //   maxCrossAxisExtent: 400.0,
        //   mainAxisSpacing: 10.0,
        //   crossAxisSpacing: 10.0,
        //   childAspectRatio: 4.0,
        // ),
      ),

      ///SliverChildListDelegate:适用于有固定数量的item的List
      ///SliverChildBuilderDelegate:适用于不固定数量的item的List

      SliverList(
        delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
          return Container(
            alignment: Alignment.center,
            color: Colors.lightBlue[100 * (index % 9)],
            child: Text(
              'SliverList item $index',
              style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
            ),
          );
        }, childCount: 20),
      ),
      // 可以伸缩滚动的头部
      SliverPersistentHeader(
        delegate: _SliverAppBarDelegate(
          minHeight: 60.0,
          maxHeight: 180.0,
          child: Container(
            color: Colors.pink,
            child: Image.asset("assets/image_appbar.jpg",fit: BoxFit.cover,),
          ),
        ),
      ),

      ///指定item高度的List
      SliverFixedExtentList(
        ///item固定高度
        itemExtent: 50,
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return Container(
              alignment: Alignment.center,
              color: Colors.lightBlue[100 * (index % 9)],
              child: Text(
                'list item $index',
                style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
              ),
            );
          },
          childCount: 20,
        ),
      ),
      SliverList(
        delegate: SliverChildListDelegate(<Widget>[
          Text(
            "SliverList SliverChildListDelegate",
            style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
          ),
          Image.asset("assets/flutter-mark-square-64.png"),
        ]),
      ),
      // SliverPadding周围可以设置内边距
      SliverPadding(
        padding: const EdgeInsets.all(10.0),
        sliver: SliverList(
          delegate: SliverChildListDelegate(<Widget>[
            Text(
              "SliverPadding SliverChildListDelegate",
              style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
            ),
            Image.asset("assets/flutter-mark-square-64.png"),
          ]),
        ),
      ),
      // SliverToBoxAdapter内部可以放置任意Widget
      SliverToBoxAdapter(
        child: Text(
          "SliverToBoxAdapter",
          style: TextStyle(fontSize: 16, decoration: TextDecoration.none),
        ),
      ),
    ],
  );
}

运行效果如图:

CustomScrollView

2.ListView Widget

接下来我们看下 ListView 组件用法,很简单,ListView 主要实现线性列表布局,可以横向或者纵向。先看下ListView 的继承关系:

ListView -> BoxScrollView -> ScrollView

ListView 也是继承自 ScrollView 组件,扩展了 ScrollView 的特点。

看下 ListView 的构造方法:

ListView({
    Key key,
    // 滚动排列方向
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    // 物理滑动响应动画
    ScrollPhysics physics,
    // 是否根据子widget的总高度/长度来设置ListView的长度,默认值为false
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    // item固定高度
    this.itemExtent,
    // 是否将item包裹在AutomaticKeepAlive widget中
    bool addAutomaticKeepAlives = true,
    // 是否将item包裹在RepaintBoundary中
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    // 子item元素
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  })

接下来通过一个实例来看下 ListView 的用法:

//ListView最简单的用法
ListView(
  shrinkWrap: true,
  padding: const EdgeInsets.all(20.0),
  children: <Widget>[
    const Text('I\'m dedicating every day to you'),
    const Text('Domestic life was never quite my style'),
    const Text('When you smile, you knock me out, I fall apart'),
    const Text('And I thought I was so smart'),
  ],
),

// 稍复杂用法
// 定义一个List
List<String> items = <String>[
  'A',
  'B',
  'C',
  'D',
  'E',
  'F',
  'G',
  'H',
  'I',
  'J',
  'K',
  'L',
  'M',
  'N',
  'O',
];

// 定义个枚举来设置item显示几行及类型
enum _MaterialListType {
  oneLine,

  oneLineWithAvatar,

  twoLine,

  threeLine,
}

class ListViewSamples extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ListViewSamplesState();
  }
}

class ListViewSamplesState extends State<ListViewSamples> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ListView'),
      ),
      body: listView4(),
    );
  }

  ///最简单的ListView
  Widget listView1() {
    return ListView(
      children: <Widget>[
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
        Text(
          'data',
          style: TextStyle(fontSize: 30),
        ),
      ],
    );
  }

  ///动态封装ListView,使用ListTile作为item
  Widget listView2() {
    // listTiles为item布局集合
    Iterable<Widget> listTiles = items.map<Widget>((String string) {
      return getItem(string);
    });
    ListTile.divideTiles(context: context, tiles: listTiles);
    return ListView(
      children: listTiles.toList(),
    );
  }

  ///使用ListView.builder构造
  Widget listView3() {
    // item widget集合
    return ListView.builder(
      // 设置item数量
      itemCount: items.length,
      itemBuilder: (BuildContext context, int position) {
        return getItem(items.elementAt(position));
      },
    );
  }

  ///ListView.custom构建ListView
  Widget listView4() {
    ///SliverChildListDelegate:适用于有固定数量的item的List
    ///SliverChildBuilderDelegate:适用于不固定数量的item的List
    return ListView.custom(
      // 设置item构建属性
      childrenDelegate:
          SliverChildBuilderDelegate((BuildContext context, int index) {
        // 返回item布局
        return ListTile(
          isThreeLine: true,
          dense: true,
          leading: ExcludeSemantics(
              child: CircleAvatar(child: Text(items.elementAt(index)))),
          title: Text('This item represents .'),
          subtitle: Text("$index"),
          trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
        );
      }, childCount: 13),
    );
  }

  ///ListView.separated构建ListView
  Widget listView5() {
    // 有分隔线
    return ListView.separated(
      // item数量
      itemCount: items.length,
      // 分隔线属性设置
      separatorBuilder: (BuildContext context, int index) {
        return Container(height: 1, color: Colors.pink);
      },
      // 构建item布局
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          isThreeLine: true,
          dense: true,
          leading: ExcludeSemantics(
              child: CircleAvatar(child: Text(items.elementAt(index)))),
          title: Text('This item represents .'),
          subtitle: Text(items.elementAt(index)),
          trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
        );
      },
    );
  }

  // 用来获取item布局的方法
  Widget getItem(String item) {
    // if (i.isOdd) {
    //   return Divider();
    // }
    return GestureDetector(
      child: Padding(
        padding: EdgeInsets.all(10.0),
        child: ListTile(
            dense: true,
            title: Text('Two-line ' + item),
            trailing: Radio<_MaterialListType>(
              value: _MaterialListType.twoLine,
              groupValue: _MaterialListType.twoLine,
              onChanged: changeItemType,
            )),
      ),
      onTap: () {
        setState(() {
          // print('row $i');
        });
      },
      onLongPress: () {},
    );
  }

  void changeItemType(_MaterialListType type) {
    print("changeItemType");
  }
}

运行效果如图:
在这里插入图片描述

3.GridView Widget

GridView 的用法和 ListView 类似,可以对比着学习,就是实现网格列表的 item 排列效果。先看下 GridView 的继承关系:

GridView -> BoxScrollView -> ScrollView

GridView 也是继承自 ScrollView 组件,扩展了 ScrollView 的特点。

看下 GridView 的构造方法:

GridView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    // 控制GridView的item如何排列
    @required this.gridDelegate,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    // item集合
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
  })

我们通过一个实例来看下 GridView 用法:

//GridView简单的用法
body: GridView.count(
        primary: false,
        padding: const EdgeInsets.all(20.0),
        crossAxisSpacing: 10.0,
        // 每行多少个item
        crossAxisCount: 2,
        children: <Widget>[
          const Text('He\'d have you all unravel at the'),
          const Text('Heed not the rabble'),
          const Text('Sound of screams but the'),
          const Text('Who scream'),
          const Text('Revolution is coming...'),
          const Text('Revolution, they...'),
        ],
      ),

// 稍微复杂点用法
class GridViewSamplesState extends State<GridViewSamples> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GridView'),
        backgroundColor: Colors.teal,
        primary: true,
      ),
      body: gridView1(),
    );
  }
  // 构造GriView方式1
  Widget gridView1() {
    return GridView(
      ///设置Grid属性:
      ///SliverGridDelegateWithMaxCrossAxisExtent:
      ///按照设置最大扩展宽度计算item个数
      ///SliverGridDelegateWithFixedCrossAxisCount:
      ///可以固定设置每行item个数
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
      ),
      children: <Widget>[
        Image.asset(
          'assets/image_appbar.jpg',
          fit: BoxFit.cover,
        ),
        Image.asset(
          'assets/image_appbar.jpg',
          fit: BoxFit.cover,
        ),
        Image.asset(
          'assets/image_appbar.jpg',
          fit: BoxFit.cover,
        ),
        Image.asset(
          'assets/image_appbar.jpg',
          fit: BoxFit.cover,
        ),
        Image.asset(
          'assets/image_appbar.jpg',
          fit: BoxFit.cover,
        ),
      ],
    );
  }
  // 构造GriView方式2
  Widget gridView2() {
    return GridView.builder(
      // item总数
      itemCount: 20,
      // 构建item
      itemBuilder: (BuildContext context, int index) {
        // GridTile可以构造带有头部、底部、中间内容的item
        return GridTile(
          header: GridTileBar(
            title: Text(
              'header',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
            leading: Icon(
              Icons.star,
              color: Colors.white,
            ),
          ),
          child: Image.asset('assets/image_appbar.jpg'),
          footer: GridTileBar(
            title: Text(
              'bottom',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
          ),
        );
      },
      // GridView排列属性设置
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
      ),
    );
  }
  // 构造GriView方式3
  Widget gridView3() {
    return GridView.custom(
      // 设置GridView属性
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 10,
        mainAxisSpacing: 10,
        childAspectRatio: 2,
      ),
      // 设置item属性
      childrenDelegate:
          SliverChildBuilderDelegate((BuildContext context, int index) {
        return Container(
          child: Text(
            'GridTile',
            style: TextStyle(fontSize: 16),
          ),
        );
      }, childCount: 20),
    );
  }
  // 构造GriView方式4
  Widget gridView4() {
    return GridView.count(
      crossAxisCount: 2,
      mainAxisSpacing: 10,
      crossAxisSpacing: 10,
      childAspectRatio: 1,
      children: <Widget>[
        GridTile(
          child: Image.asset('assets/image_appbar.jpg'),
        ),
        GridTile(
          child: Image.asset('assets/image_appbar.jpg'),
        ),
        GridTile(
          child: Image.asset('assets/image_appbar.jpg'),
        ),
        GridTile(
          child: Image.asset('assets/image_appbar.jpg'),
        ),
        GridTile(
          child: Image.asset('assets/image_appbar.jpg'),
        ),
      ],
    );
  }

  // 构造GriView方式5
  ///GridView.extent构建GridView,根据最大宽度自动计算item数量
  Widget gridView5() {
    return GridView.extent(
      // item最大宽度
      maxCrossAxisExtent: 150,
      crossAxisSpacing: 10,
      mainAxisSpacing: 10,
      childAspectRatio: 1,
      children: <Widget>[
        GridTile(
          header: GridTileBar(
            title: Text(
              'header',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
            leading: Icon(
              Icons.star,
              color: Colors.white,
            ),
          ),
          child: Image.asset('assets/image_appbar.jpg'),
          footer: GridTileBar(
            title: Text(
              'bottom',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
          ),
        ),
        GridTile(
          header: GridTileBar(
            title: Text(
              'header',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
            leading: Icon(
              Icons.star,
              color: Colors.white,
            ),
          ),
          child: Image.asset('assets/image_appbar.jpg'),
          footer: GridTileBar(
            title: Text(
              'bottom',
              style: TextStyle(fontSize: 20),
            ),
            backgroundColor: Colors.black45,
          ),
        ),
      ],
    );
  }
}

运行效果如图:

GridView

4.ScrollView

ScrollView 这个 Widget 并不直接单独使用,一般是使用它的扩展类,如 CustomScrollView、ListView、GridView 等等。这里将他们相关的通用特性讲一下:

滚动条样式的设置。一般滚动类组件滚动时,我们希望侧边显示一个滚动条,那么我们可以使用 Scrollbar 进行包裹:

///Scrollbar
Widget scroll1() {
  return Scrollbar(
    child: ListView.separated(
      itemCount: 20,
      separatorBuilder: (BuildContext context, int index) {
        return Container(height: 1, color: Colors.black87);
      },
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          isThreeLine: true,
          dense: true,
          leading:
              ExcludeSemantics(child: CircleAvatar(child: Text('leading'))),
          title: Text('This item represents .'),
          subtitle: Text('subtitle'),
          trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
        );
      },
    ),
  );
}

单子元素的 ScrollView(SingleChildScrollView),类似于 Android 里的 ScrollView,内部只能包裹一个子控件:

///SingleChildScrollView
Widget scroll2() {
  // 只能放置一个元素
  return SingleChildScrollView(
    child: Column(
      children: <Widget>[
        Container(
          // A fixed-height child.
          color: Colors.yellow,
          height: 620.0,
        ),
        Container(
          color: Colors.orange,
          height: 720.0,
        ),
      ],
    ),
  );
}

有的时候我们需要自定义放置在内部的子元素排列方式,可以使用 CustomMultiChildLayout 或 CustomSingleChildLayout,这两个不常用,大家了解即可:

// CustomMultiChildLayout
Widget scroll5() {
  return CustomMultiChildLayout(
    delegate: MultiChildDelegate(),
    children: <Widget>[
      LayoutId(
        id: MultiChildDelegate.title,
        child: Container(
          color: Colors.teal,
          child: Text('data1'),
        ),
      ),
      LayoutId(
        id: MultiChildDelegate.description,
        child: Container(
          color: Colors.amber,
          child: Text('data2'),
        ),
      ),
    ],
  );
}

class MultiChildDelegate extends MultiChildLayoutDelegate {
  static const String title = 'title';
  static const String description = 'description';

  @override
  void performLayout(Size size) {
    ///约束
    BoxConstraints boxConstraints = BoxConstraints(maxWidth: size.width);

    ///绑定约束
    Size titleSize = layoutChild(title, boxConstraints);

    ///位置
    positionChild(title, Offset(0, 0));
    Size descriptionSize = layoutChild(description, boxConstraints);
    double descriptionY = titleSize.height;
    positionChild(description, Offset(10, descriptionY));
  }

  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
    return oldDelegate == this;
  }
}

// CustomSingleChildLayout
Widget scroll6() {
  return CustomSingleChildLayout(
    delegate: SingleChildDelegate(Size(300, 200)),
    child: Container(
      color: Colors.teal,
      child: Text('data'),
    ),
  );
}

class SingleChildDelegate extends SingleChildLayoutDelegate {
  Size size;
  SingleChildDelegate(this.size);

  @override
  Size getSize(BoxConstraints constraints) {
    // return super.getSize(constraints);
    return size;
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    // return super.getConstraintsForChild(constraints);
    return BoxConstraints.tight(size);
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    // return super.getPositionForChild(size, childSize);
    return Offset(30, 20);
  }

  @override
  bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
    return oldDelegate == this;
  }
}

5.ExpansionPanel Widget

ExpansionPanel 用来实现类似于 QQ 分组的一个组件,一般搭配 ExpansionPanelList 一起使用。

ExpansionPanelList 继承自 StatefulWidget,是有状态组件。

我们接下来看下 ExpansionPanelList 的构造方法:

const ExpansionPanelList({
    Key key,
    // 子元素ExpansionPanel集合
    this.children = const <ExpansionPanel>[],
    // 展开/关闭回调
    this.expansionCallback,
    // 展开动画执行时长
    this.animationDuration = kThemeAnimationDuration,
  })

然后是 ExpansionPanel 的构造方法:

ExpansionPanel({
    // 标题构造器
    @required this.headerBuilder,
    // 内容区域
    @required this.body,
    // 是否展开
    this.isExpanded = false
  }) 

接下来通过一个实例来看下 ExpansionPanelList 和 ExpansionPanel 的用法:

class ExpansionPanelListPageState extends State<ExpansionPanelListPage> {
  List<ExpansionPanelItem> _expansionPanelItems;
  @override
  void initState() {
    super.initState();
    getExpansionPanelList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ExpansionPanelList"),
      ),
      body: Container(
        padding: EdgeInsets.all(10.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            // ExpansionPanelList
            ExpansionPanelList(
              // 动态创建ExpansionPanel item布局
              children: _expansionPanelItems.map((ExpansionPanelItem item) {
                return ExpansionPanel(
                  // 头部区域
                  headerBuilder: (BuildContext context, bool isExpanded) {
                    return Container(
                      padding: EdgeInsets.all(10.0),
                      child: Text(
                        item.headerText,
                        style: Theme.of(context).textTheme.title,
                      ),
                    );
                  },
                  // 内容区域
                  body: item.body,
                  // 面板是否展开
                  isExpanded: item.isExpand,
                );
              }).toList(),
              // 面板展开/关闭回调
              expansionCallback: (int panelIndex, bool isExpanded) {
                setState(() {
                  _expansionPanelItems[panelIndex].isExpand = !isExpanded;
                });
              },
            )
          ],
        ),
      ),
    );
  }
  // 创建面板item布局的集合
  List<ExpansionPanelItem> getExpansionPanelList() {
    _expansionPanelItems = new List();
    for (int i = 0; i < 5; i++) {
      _expansionPanelItems.add(ExpansionPanelItem(
        headerText: 'Panel B',
        body: Container(
          padding: EdgeInsets.all(10.0),
          width: double.infinity,
          child: Text('Content for Panel $i \n The Content'),
        ),
        isExpand: false,
      ));
    }
    _expansionPanelItems.length = 5;
    return _expansionPanelItems;
  }
}

// 自定义item
class ExpansionPanelItem {
  final String headerText;
  final Widget body;
  bool isExpand;
  ExpansionPanelItem({
    this.headerText,
    this.body,
    this.isExpand,
  });
}

// 简单用法

class ExpansionPanelPageState extends State<ExpansionPanelPage> {
  // 面板是否展开状态保存
  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ExpansionPanelList"),
      ),
      body: Container(
        padding: EdgeInsets.all(10.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // ExpansionPanelList
            ExpansionPanelList(
              children: <ExpansionPanel>[
                // ExpansionPanel
                ExpansionPanel(
                  // 头部
                  headerBuilder: (BuildContext context, bool isExpanded) {
                    return Container(
                      padding: EdgeInsets.all(10.0),
                      child: Text(
                        'Panel A',
                        style: Theme.of(context).textTheme.title,
                      ),
                    );
                  },
                  // 展开的内容区域
                  body: Container(
                    padding: EdgeInsets.all(10.0),
                    width: double.infinity,
                    child: Text('Content for Panel A\n The Content'),
                  ),
                  // 是否展开
                  isExpanded: _isExpanded,
                ),
                ExpansionPanel(
                  headerBuilder: (BuildContext context, bool isExpanded) {
                    return Container(
                      padding: EdgeInsets.all(10.0),
                      child: Text(
                        'Panel B',
                        style: Theme.of(context).textTheme.title,
                      ),
                    );
                  },
                  body: Container(
                    padding: EdgeInsets.all(10.0),
                    width: double.infinity,
                    child: Text('Content for Panel B\n The Content'),
                  ),
                  isExpanded: _isExpanded,
                )
              ],
              // 展开或关闭回调
              expansionCallback: (int panelIndex, bool isExpanded) {
                setState(() {
                  _isExpanded = !isExpanded;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

ExpansionPanelList

ExpansionPanelList 和 ExpansionPanel 并不属于滚动组件,只不过看起来类似。这里放在一起顺便讲解。

6.总结

本节博客主要是给大家讲解了 Flutter 的几个常用的列表、滚动布局组件 Widget 的用法和特点。主要注意点和建议如下:

  • 重点掌握 CustomScrollView、ListView 和 GridView 用法。
  • 实践一下这几个 Widget 使用方法,尝试写一个可以滚动的列表页面。
发布了253 篇原创文章 · 获赞 52 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41151659/article/details/103381763