Flutter入门(36):Flutter 组件之 Expans
1. 基本介绍
ExpansionPanel、ExpansionPanelRadio 是一种常见的折叠框。
ExpansionPanelList 是承载折叠框的一个父类控件。
2. 示例代码
代码下载地址。如果对你有帮助的话记得给个关注,代码会根据我的 Flutter 专题不断更新。
3. 属性介绍
ExpansionPanel 属性 | 介绍 |
---|---|
headerBuilder | @required,Header Widget 构造方法 |
body | @required,展开部分 Widget |
isExpanded | 是否展开,默认为 false |
canTapOnHeader | 是否可以点击 header 用来展开收起,false |
ExpansionPanelRadio属性 | 介绍 |
---|---|
value | @required 唯一标识 |
headerBuilder | @required Header Widget 构造方法 |
body | @required,展开部分 Widget |
canTapOnHeader | 是否可以点击 header 用来展开收起,false |
ExpansionPanelList属性 | 介绍 |
---|---|
children | 子控件数组,类型为 <ExpansionPanel> 的数组 |
expansionCallback | 点击折叠收起回调函数,(index, isExpand){},返回当前下标以及是否折叠 |
animationDuration | 动画时间,默认为 kThemeAnimationDuration |
expandedHeaderPadding | 展开后 Header 的 padding,默认为 _kPanelHeaderExpandedDefaultPadding |
dividerColor | 分割线颜色 |
ExpansionPanelList.radio 属性 | 介绍
children | 子控件数组,类型为 <ExpansionPanelRadio> 的数组
expansionCallback | 点击折叠收起回调函数,(index, isExpand){},返回当前下标以及是否折叠
initialOpenPanelValue | 当前选中标识,initialOpenPanelValue == ExpansionPanelRadio.value 时,该 ExpansionPanelRadio 会默认展开
animationDuration | 动画时间,默认为 kThemeAnimationDuration
expandedHeaderPadding | 展开后 Header 的 padding,默认为 _kPanelHeaderExpandedDefaultPadding
dividerColor | 分割线颜色
4. ExpansionPanel,ExpansionPanelList 详解
4.1 代码实现
ExpansionPanel 是单个折叠框,他的效果实现还需要依托于 ExpansionPanelList,这边直接上 demo,一个简单的折叠框的实现。
import 'package:flutter/material.dart';
class FMExpansionPanelVC extends StatefulWidget{
@override
FMExpansionPanelState createState() => FMExpansionPanelState();
}
class FMExpansionPanelState extends State <FMExpansionPanelVC>{
List <ExpansionPanelModel> _models = [];
List <ExpansionPanel> _childrenForExpansionPanel = [];
List <ExpansionPanelRadio> _childrenForExpansionPanelRadio = [];
@override
void initState(){
super.initState();
_initData();
}
void _initData(){
_models.clear();
_models.add(ExpansionPanelModel("EP1", "Title EP1", false));
_models.add(ExpansionPanelModel("EP2", "Title EP2", false));
_models.add(ExpansionPanelModel("EP3", "Title EP3", false));
_models.add(ExpansionPanelModel("EP4", "Title EP4", false));
_models.add(ExpansionPanelModel("EP5", "Title EP5", false));
print("initDate");
}
void _initChildrenForExpansionPanel(){
_childrenForExpansionPanel.clear();
_models.forEach((model) {
_childrenForExpansionPanel.add(_expansionPanel(model));
});
}
void _initChildrenForExpansionPanelRadio(){
_childrenForExpansionPanelRadio.clear();
_models.forEach((model) {
_childrenForExpansionPanelRadio.add(_expansionPanelRadio(model));
});
print(_childrenForExpansionPanelRadio);
}
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanel"),
),
body: SingleChildScrollView(
child: _expansionPanelList(),
),
);
}
ExpansionPanelList _expansionPanelList(){
_initChildrenForExpansionPanel();
return ExpansionPanelList(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
print(_models[index].isExpanded);
setState(() {
});
},
dividerColor: Colors.black,
expandedHeaderPadding: EdgeInsets.zero,
children: _childrenForExpansionPanel,
);
}
ExpansionPanel _expansionPanel(ExpansionPanelModel model){
return ExpansionPanel(
headerBuilder: (context, boolValue){
return Container(
height: 80,
alignment: Alignment.centerLeft,
child: Text("${model.title}"),
);
},
isExpanded: model.isExpanded,
canTapOnHeader: true,
body: Container(
height: 200,
color: Colors.red,
),
);
}
ExpansionPanelList _expansionPanelListRadio(){
_initChildrenForExpansionPanelRadio();
return ExpansionPanelList.radio(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
print(_models[index].isExpanded);
setState(() {
});
},
dividerColor: Colors.black,
expandedHeaderPadding: EdgeInsets.zero,
children: _childrenForExpansionPanelRadio,
);
}
ExpansionPanelRadio _expansionPanelRadio(ExpansionPanelModel model){
return ExpansionPanelRadio(
value: model.value,
headerBuilder: (context, boolValue){
return Container(
height: 80,
alignment: Alignment.centerLeft,
child: Text("${model.title}"),
);
},
canTapOnHeader: true,
body: Container(
height: 200,
color: Colors.red,
),
);
}
}
class ExpansionPanelModel {
var value;
String title;
bool isExpanded;
ExpansionPanelModel(this.value, this.title, this.isExpanded);
}
ExpansionPanel normal.gif
4.2 ExpansionPanelList 常见属性效果
ExpansionPanelList _expansionPanelList(){
_initChildrenForExpansionPanel();
return ExpansionPanelList(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
print(_models[index].isExpanded);
setState(() {
});
},
dividerColor: Colors.green,
expandedHeaderPadding: EdgeInsets.all(30),
children: _childrenForExpansionPanel,
);
}
我们设置 dividerColor 为 green。
dividerColor.png我们设置 expandedHeaderPadding 为 EdgeInsets.all(30),可以看到展开后 Header 的 Text 组件的位置做出了相应改变。
expandedHeaderPadding.png5. ExpansionPanelRadio、ExpansionPanelList.radio 详解
5.1 代码实现
相比上方 ExpansionPanel、ExpansionPanelList 其实只是换一种实现方式,他们唯一的差别是使用 Radio 方式,可以为设置默认展开其中一个 ExpansionPanelRadio。
import 'package:flutter/material.dart';
class FMExpansionPanelVC extends StatefulWidget{
@override
FMExpansionPanelState createState() => FMExpansionPanelState();
}
class FMExpansionPanelState extends State <FMExpansionPanelVC>{
List <ExpansionPanelModel> _models = [];
List <ExpansionPanel> _childrenForExpansionPanel = [];
List <ExpansionPanelRadio> _childrenForExpansionPanelRadio = [];
@override
void initState(){
super.initState();
_initData();
}
void _initData(){
_models.clear();
_models.add(ExpansionPanelModel("EP1", "Title EP1", false));
_models.add(ExpansionPanelModel("EP2", "Title EP2", false));
_models.add(ExpansionPanelModel("EP3", "Title EP3", false));
_models.add(ExpansionPanelModel("EP4", "Title EP4", false));
_models.add(ExpansionPanelModel("EP5", "Title EP5", false));
print("initDate");
}
void _initChildrenForExpansionPanel(){
_childrenForExpansionPanel.clear();
_models.forEach((model) {
_childrenForExpansionPanel.add(_expansionPanel(model));
});
}
void _initChildrenForExpansionPanelRadio(){
_childrenForExpansionPanelRadio.clear();
_models.forEach((model) {
_childrenForExpansionPanelRadio.add(_expansionPanelRadio(model));
});
print(_childrenForExpansionPanelRadio);
}
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanel"),
),
body: SingleChildScrollView(
// child: _expansionPanelList(),
child: _expansionPanelListRadio(),
),
);
}
ExpansionPanelList _expansionPanelList(){
_initChildrenForExpansionPanel();
return ExpansionPanelList(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
print(_models[index].isExpanded);
setState(() {
});
},
dividerColor: Colors.black,
expandedHeaderPadding: EdgeInsets.zero,
children: _childrenForExpansionPanel,
);
}
ExpansionPanel _expansionPanel(ExpansionPanelModel model){
return ExpansionPanel(
headerBuilder: (context, boolValue){
return Container(
height: 80,
alignment: Alignment.centerLeft,
child: Text("${model.title}"),
);
},
isExpanded: model.isExpanded,
canTapOnHeader: true,
body: Container(
height: 200,
color: Colors.red,
),
);
}
ExpansionPanelList _expansionPanelListRadio(){
_initChildrenForExpansionPanelRadio();
return ExpansionPanelList.radio(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
print(_models[index].isExpanded);
setState(() {
});
},
dividerColor: Colors.black,
expandedHeaderPadding: EdgeInsets.zero,
children: _childrenForExpansionPanelRadio,
);
}
ExpansionPanelRadio _expansionPanelRadio(ExpansionPanelModel model){
return ExpansionPanelRadio(
value: model.value,
headerBuilder: (context, boolValue){
return Container(
height: 80,
alignment: Alignment.centerLeft,
child: Text("${model.title}"),
);
},
canTapOnHeader: true,
body: Container(
height: 200,
color: Colors.red,
),
);
}
}
class ExpansionPanelModel {
var value;
String title;
bool isExpanded;
ExpansionPanelModel(this.value, this.title, this.isExpanded){
}
}
ExpansionPanel radio.gif
5.2 设置默认展开 ExpansionPanelRadio
它的原理其实很简单,设置 ExpansionPanelRadio.value,可以是字符串,也可以是数字,作为唯一识别标识。注意每个 value 不可以重复,否则会报错。
All ExpansionPanelRadio identifier values must be unique.
'package:flutter/src/material/expansion_panel.dart':
Failed assertion: line 385 pos 14: '_allIdentifiersUnique()'
我们对示例代码进行以下改动,然后重新进入页面,此处热重载不会更新页面。
ExpansionPanelList _expansionPanelListRadio(){
_initChildrenForExpansionPanelRadio();
return ExpansionPanelList.radio(
expansionCallback: (index, isExpand){
_models[index].isExpanded = !_models[index].isExpanded;
setState(() {
});
},
initialOpenPanelValue: _models[2].value,
dividerColor: Colors.black,
expandedHeaderPadding: EdgeInsets.zero,
children: _childrenForExpansionPanelRadio,
);
}
ExpansionPanelRadio initialOpenPanelValue.png
6. 技术小结
- 注意 ExpansionPanel 不可以直接作为子控件给到 body,否则会报错,这里用了 SingleChildScrollView 作为外层容器,使用 Column、ListView...等很多控件都可以。
- 注意使用 ExpansionPanelList() 与 ExpansionPanelList.radio() 创建出来的 Widget,他们的 children 分别对应 <ExpansionPanel>,<ExpansionPanelRadio>,此处容易报错。
- 注意 ExpansionPanelRadio.value 不可以相同,容易报错。