在实际业务开发过程中,或多或少会遇到树形控件的需求。

最简单的需求比如 QQ 联系人的分组:

类似于这种,Flutter 给我们提供了相当便捷的 UI 组件 ExpansionPanel。

ExpansionPanel

看名字也能看出来,是一个"扩展面板"。

那按照惯例,我们首先打开官网,查看一下它的说明:

A material expansion panel. It has a header and a body and can be either expanded or collapsed. The body of the panel is only visible when it is expanded.

Expansion panels are only intended to be used as children for ExpansionPanelList.

一个material 扩展面板。它有一个 header 和一个 body ,可以展开或折叠。面板的 body 仅在展开时可见。

扩展面板仅用作于 ExpansionPanelList。

看说明也就能明白了,它不单独使用,只能和 ExpansionPanelList 配合使用。

那我们点进源码看一下构造函数:

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);

一共有四个参数:

  • headerBuilder:header

  • body:body

  • isExpanded:是否展开

  • canTapOnHeader:header是否可以点击

看完了 ExpansionPanel 的构造函数,下面就看一下 ExpansionPanelList

ExpansionPanelList

照例先看它的介绍:

A material expansion panel list that lays out its children and animates expansions.

material 展开面板列表,用于设置其子项并为展开设置动画。

然后打开源码查看构造函数:

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);

需要我们使用的也就三个参数:

  • children:不用多说,就是 ExpansionPanel

  • expansionCallback:展开回调,这里会返回点击的 index

  • animationDuration:动画的时间

基本上看完构造函数,我们也就知道该怎么去写代码了,那官方也提供给我们了一个 Demo。

官方Demo

效果如下:

来看下代码:

class Item {  Item({  this.expandedValue, this.headerValue,   this.isExpanded = false,   }); String expandedValue;   String headerValue; bool isExpanded;
}
List<Item> generateItems(int numberOfItems) { return List.generate(numberOfItems, (int index) {   return Item(    headerValue: 'Panel $index',  expandedValue: 'This is item number $index',  );  });
}
class ExpansionPanelPage extends StatefulWidget {   ExpansionPanelPage({Key key}) : super(key: key);    @override  _ExpansionPanelPageState createState() => _ExpansionPanelPageState();
}
class _ExpansionPanelPageState extends State<ExpansionPanelPage> {    List<Item> _data = generateItems(8); @override  Widget build(BuildContext context) {    return Scaffold(    appBar: AppBar( title: Text('ExpansionPanelPage'),    ),  body: SingleChildScrollView(    child: Container(   child: _buildPanel(),   ),  ),  );  }   Widget _buildPanel() {  return ExpansionPanelList(  expansionCallback: (int index, bool isExpanded) {   setState(() {   _data[index].isExpanded = !isExpanded; }); },  children: _data.map<ExpansionPanel>((Item item) { return ExpansionPanel(  headerBuilder: (BuildContext context, bool isExpanded) {    return ListTile(    title: Text(item.headerValue),  );  },  body: ListTile( title: Text(item.expandedValue),    subtitle: Text('To delete this panel, tap the trash can icon'),   trailing: Icon(Icons.delete),   onTap: () { setState(() {   _data.removeWhere((currentItem) => item == currentItem);  }); }), isExpanded: item.isExpanded,    );  }).toList(),    );  }
}

从上往下看。

Item

首先定义了一个 Item 类,里面包含了:

  • expandedValue:展开的值

  • headerValue:header的值

  • isExpanded:是否已经展开

generateItems

生成指定数量的 Item

_ExpansionPanelPageState

重点来了,看build 方法:

@override
Widget build(BuildContext context) {    return Scaffold(    appBar: AppBar( title: Text('ExpansionPanelPage'),    ),  body: SingleChildScrollView(    child: Container(   child: _buildPanel(),   ),  ),  );
}

_buildPanel()方法就是根据 Item 的数量生成一个 ExpansionPanelList

那为什么要用 SingleChildScrollView 包起来?

我们先把 SingleChildScrollView 去掉来看一下效果:

发现什么都没有了,看一下log:

flutter: The following assertion was thrown during performLayout(): flutter: RenderListBody must have unlimited space along its main axis. flutter: RenderListBody does not clip or resize its children, so it must be placed in a parent that does not flutter: constrain the main axis. You probably want to put the RenderListBody inside a RenderViewport with a matching main axis.

大致意思就是说:

RenderListBody所在的主轴必须要有无线的空间,因为RenderListBody 要不断的调整children 的大小,所以必须把它放在不约束主轴的 parent 中。

在上面的gif图我们也能看出来,只有点击箭头才能展开,如果想要点击 header 也要展开的话,

使用 ExpansionPanel 的 canTapOnHeader 参数:

ExpansionPanel(  canTapOnHeader: true,   headerBuilder: xxx, body: xxx;
)

效果如下:

body is ListView

在我们实际业务中,可能最多的业务为展开是一个列表,那需要 body 是ListView。

其实和官方Demo差不多,需要注意的一点就是 shrinkWrap & physics 这两个字段:

return ListView.builder( shrinkWrap: true,   physics: NeverScrollableScrollPhysics(),
);

只能展开一个

有时我们也会遇到只能展开一个,点击其他的时候要关闭已经展开的。

效果如下:

代码如下,需使用 ExpansionPanelList.radio

Widget _buildPanel() { return ExpansionPanelList.radio(    expansionCallback: (int index, bool isExpanded) {   setState(() {   _data[index].isExpanded = !isExpanded; }); },  children: _data.map<ExpansionPanel>((Item item) { return ExpansionPanelRadio( canTapOnHeader: true,   headerBuilder: (BuildContext context, bool isExpanded) {    return ListTile(    title: Text(item.headerValue),  );  },  body: ListTile( title: Text(item.expandedValue),    subtitle: Text('To delete this panel, tap the trash can icon'),   trailing: Icon(Icons.delete),   onTap: () { setState(() {   _data.removeWhere((currentItem) => item == currentItem);  }); }), value: item.headerValue,    );  }).toList(),    );
}

ExpansionPanelList.radio 的 children 也需要改变为: ExpansionPanelRadio

ExpansionPanelRadioExpansionPanel 的区别就是一个 value。

ExpansionPanelRadio 是用 value 来区分的,所以每一个要是唯一的。

总结

使用 ExpansionPanel 可以很轻松的实现展开效果,

而且 ExpansionPanelList 返回的是一个 MergeableMaterial,

所以想自定义UI的,也可以自己实现。

完整代码已经传至GitHub:https://github.com/wanglu1209/WFlutterDemo

Flutter ExpansionPanel 超级实用展开控件相关推荐

  1. flutter 动画展开菜单_Flutter ExpansionPanel 超级实用展开控件

    在实际业务开发过程中,或多或少会遇到树形控件的需求. 最简单的需求比如 联系人的分组: 类似于这种,Flutter 给我们提供了相当便捷的 UI 组件 ExpansionPanel. Expansio ...

  2. Flutter Container、Center设置控件居中背景及其他属性

    Flutter Container.Center设置控件居中背景及其他属性 //控件可以居中Container(alignment: Alignment.bottomCenter,//设置控件内容的位 ...

  3. Flutter 一个电话样式小控件

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

  4. Flutter进阶—创建有状态控件

    Flutter进阶-构建布局实例展示了如何创建以下布局. 当应用程序首次启动时,这颗实心星标是红色的,表明这个景点曾经被收藏过.实心星标旁边的人数表明,有66人喜欢这个景点.现在需要完成一个任务,点击 ...

  5. Android自己写的三款实用开关控件

    2019独角兽企业重金招聘Python工程师标准>>> 自定义开关控件,代码简单,比较实用. http://www.see-source.com/androidwidget/list ...

  6. flutter初体验之基础控件知识

    在naive开发中,大家对view肯定很熟悉,它代表着所有控件的祖先.在flutter中,也存在这么一个所有控件的祖先---Widget.Widget类是一个抽象类,定义在系统的framework.d ...

  7. Flutter PullToRefresh上下拉刷新控件

    Flutter为我们提供了相当丰富的控件,但是有时候还是不能满足大家的需求,官方的控件只有下拉刷新,没有上拉加载,感觉很别扭:我从安卓转过来的,所以我做了一套和安卓原生的一样效果的控件 首先请允许我介 ...

  8. Android-UI 超级优良超级多超级强大开源控件源码demo

    第一部分 个性化控件(View) 主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.Pro ...

  9. Android仿超级课程表开源控件

    # TimetableView(v1.x) TimetableView已经升级到v2.0.2了,新增了很多实用的功能 本文中的功能介绍.控件用法均针对v1.x,它并不是最新的,你可以在 Change ...

最新文章

  1. Python 语法小知识
  2. 2013大数据全球技术峰会观后感
  3. 【WPF】使用控件MediaElement播放视频
  4. duilib 自带树形控件的认识
  5. linux 产生0~9内的随机数
  6. 管家机器人先生txt_《管家机器人先生》(主角墨青如玉)大结局全文阅读
  7. [IDDFS+背包] 洛谷P2744 [USACO5.3]量取牛奶Milk Measuring
  8. 利用alpha matte提取图片前景
  9. MySQL恢复数据库(mysql命令)
  10. 5.23低版本到高版本问题
  11. The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals
  12. ant-vue中的a-icon使用方法
  13. GlyphRun 对象和 Glyphs 元素简介
  14. c语言luhn算法生成校验位,银行卡号码校验算法(Luhn算法,又叫模10算法)
  15. 咖说 | 「延展与重构」数字艺术的新可能
  16. JTAG篇(5) OpenOCD 写入数据到TAP
  17. 数字图像处理9--尺度空间
  18. Hello my friend
  19. 鼠标点击器20+版本使用教程系列(7)鼠标轨迹记录点击器1.1使用教程
  20. svn获取代码时报错【 C:\Users\ADMINI~1\APPData\Loca\Temp\svn.... 找不到指定路径】

热门文章

  1. LDF explorer使用问题记录
  2. CPU,内存,磁盘速度比较
  3. 批量改名文件夹中的文件名并批量添加到动易数据库
  4. javaweb发布到云服务器上之后,验证码接收不到问题
  5. python开发工程师工资_Python开发工程师工资一般多少钱
  6. 12T已达机械硬盘极限 未来突破靠氦气硬盘
  7. 医疗行业数据泄露惊人,提高医疗信息安全刻不容缓
  8. 必备的DevOps工具链大盘点
  9. android的Handler
  10. 三维图形及动画软件开发流程