Flutter 之 ExpansionPanel

4,778 阅读2分钟

介绍

ExpansionPanel官方提供的一个扩展面板的控件,使用它可以轻松实现展开收起的功能。

ExpansionPanel

  ExpansionPanel({
    @required this.headerBuilder,
    @required this.body,
    this.isExpanded = false,
    this.canTapOnHeader = false,
  }) : assert(headerBuilder != null),
       assert(body != null),
       assert(isExpanded != null),
       assert(canTapOnHeader != null);

ExpansionPanel的构造函数有4个参数:

  1. headerBuilderheader样式
  2. bodybody样式
  3. isExpanded:是否展开
  4. canTapOnHeaderheader是否可点击

ExpansionPanelList

Creates an expansion panel to be used as a child for [ExpansionPanelList].

源码注释提到ExpansionPanel不能单独使用,要作为ExpansionPanelListchild

  const ExpansionPanelList({
    Key key,
    this.children = const <ExpansionPanel>[],
    this.expansionCallback,
    this.animationDuration = kThemeAnimationDuration,
  }) : assert(children != null),
       assert(animationDuration != null),
       _allowOnlyOnePanelOpen = false,
       initialOpenPanelValue = null,
       super(key: key);

ExpansionPanelList的构造函数有3个参数:

  1. childrenExpansionPanel类型的数组
  2. expansionCallback:点击时的回调,会返回对应点击的index和当前isExpanded的值
  3. animationDuration:动画持续时间

效果

Demo代码

class Item {
  String familyName;
  List<String> fullName;
  bool isExpanded;
  
  Item(this.familyName, this.fullName, this.isExpanded);
}

List<Item> getDataSource() {
  return [
    Item('张', ['张一一', '张二二', '张三三', '张四四'], false),
    Item('王', ['王一一', '王二二', '王三三', '王四四'], false),
    Item('李', ['李一一', '李二二', '李三三', '李四四'], false),
    Item('赵', ['赵一一', '赵二二', '赵三三', '赵四四'], false),
    Item('周', ['周一一', '周二二', '周三三', '周四四'], false),
    Item('吴', ['吴一一', '吴二二', '吴三三', '吴四四'], false),
    Item('孙', ['孙一一', '孙二二', '孙三三', '孙四四'], false),
  ];
}
  List<Item> _dataSource = getDataSource();
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: ExpansionPanelList(
        expansionCallback: (int index, bool isExpanded) {
          setState(() {
            _dataSource[index].isExpanded = !isExpanded;
          });
        },
        children: _dataSource.map<ExpansionPanel>((Item item) {
          return ExpansionPanel(
            canTapOnHeader: true,
            isExpanded: item.isExpanded,
            headerBuilder: (BuildContext context, bool isExpanded) {
              return ListTile(
                title: Text(item.familyName),
              );
            },
            body: ListView.separated(
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  padding: EdgeInsets.fromLTRB(15, 10, 15, 10),
                  child: Text(item.fullName[index]),
                );
              },
              separatorBuilder: (BuildContext context, int index) {
                return new Container(height: 1.0, color: Color(0xfff2f2f2));
              },
              itemCount: item.fullName.length,
              shrinkWrap: true,
              physics: NeverScrollableScrollPhysics(),
            ),
          );
        }).toList(),
        animationDuration: Duration(microseconds: 100),
      ),
    );
  }

总结

代码简单易懂不做多解释,有几个需要注意的点这里总结下:

  • build方法直接return一个ExpansionPanelList会发现没有任何内容展示。 Log提示:RenderListBody must have unlimited space along its main axis.也就是说要保持RenderListBody的主轴上必须有无限的空间。这里我们在外面包一层SingleChildScrollView来达到主轴无限的空间的目的。

  • 注意ListView.separated中的属性设置。

  shrinkWrap: true, // 多用于嵌套`listView`内容大小不确定的情况
  physics: NeverScrollableScrollPhysics(), // 禁止滚动,这里想要的效果是展开后跟着ExpansionPanelList一起滚动而不是内部ListView单独滚动
  • 如果你的需求是只能展开一个,点击时其他展开的自动收起的情况,使用ExpansionPanelList.radio配合ExpansionPanelRadio能达到你想要的效果。ExpansionPanelRadioExpansionPanel的子类,多了一个value作为唯一标识,用来区分每个ExpansionPanelRadio