Flutter开发者

ExpansionPanelList

2018-10-10

上期回顾

在前面的文章中我们介绍了可以展开的带标题控件ExpansionTile的用法,在文章的最后还是按照惯例给大家留下了一个问题。

实现如下效果:

可以看到界面整体上是一个listView,在ListView的第二例是一个ExpansionTile,ExpansionTile的内部是多个ListTile,trailing结合自定义动画将“+”icon旋转22.5°变成了一个“×”,并且在ExpansionTile展开时改变了icon的颜色。

当然,代码实现起来也是非常的简单,不熟悉动画的童鞋可以看下公众号前面的文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

import 'package:flutter/material.dart';

void main() {
runApp(new MaterialApp(
home: MyApp(),
));
}

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
Color iconColors = Colors.grey;

@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: Duration(milliseconds: 200));
animation = new Tween(begin: 0.0, end: 0.125).animate(animationController);
}

_changeOpacity(bool expand) {
setState(() {
if (expand) {
animationController.forward();
iconColors = Colors.redAccent;
} else {
animationController.reverse();
iconColors = Colors.grey;
}
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ExpandTitle')),
body: ListView(children: <Widget>[
const ListTile(title: Text('第一列')),
ExpansionTile(
title: const Text('第二列'),
backgroundColor: Theme.of(context).accentColor.withOpacity(0.025),
trailing: RotationTransition(
turns: animation,
child: Icon(
Icons.add,
color: iconColors,
),
),
onExpansionChanged: (bool) {
_changeOpacity(bool);
},
children: const <Widget>[
ListTile(title: Text('One')),
ListTile(title: Text('Two')),
ListTile(title: Text('Free')),
ListTile(title: Text('Four'))
]),
const ListTile(title: Text('第三列')),
const ListTile(title: Text('第四列')),
const ListTile(title: Text('第五列')),
const ListTile(title: Text('第六列')),
const ListTile(title: Text('第七列')),
const ListTile(title: Text('第八列')),
]));
}
}

既然我们看过了ExpansionTile 的用法,那么我们今天再来看下ExpansionPanelList的用法吧

ExpansionPanelList

ExpansionPanel从单词的字面意思可以翻译为一个可以展开的面板,那么加上List就是包含多个可展开面板的列表啰。那么它又和前面讲过的ExpansionTile有什么区别,其实长得还是挺想的但是ExpansionPanelList在展开和关闭的时候是有动画的,比较不那么突兀。

ExpansionPanelList的构造方法:

1
2
3
4
5
6
7

ExpansionPanelList({
Key key,
this.children = const <ExpansionPanel>[],
this.expansionCallback,//展开关闭回调
this.animationDuration = kThemeAnimationDuration,//展开进行时间
})

构造方法很简单,接收ExpansionPanel类型的List集合,展开关闭的回调和展开时间三个参数。
ExpansionPanel的构造方法:

1
2
3
4
5
6

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

构造方法非常的简单,但是在这里需要注意的是ExpansionPanel不是一个Widget它是一个单独类,只能配合ExpansionPanelList使用。

国际惯例,看下最基本的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

import 'package:flutter/material.dart';

void main() {
runApp(new MaterialApp(
home: ExpansionPanelListDemo(),
));
}

class ExpansionPanelListDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ExpansionPanelListDemoState();
}
}

class ExpansionPanelListDemoState extends State<ExpansionPanelListDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanelListDemo"),
),
body: SingleChildScrollView(
child: ExpansionPanelList(
children: [
ExpansionPanel(
headerBuilder: (index, opened) {
return ListTile(
title: Text("更多内容"),
);
},
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: Container(
height: 100.0,
color: Colors.blue,
alignment: Alignment.center,
child: Icon(
Icons.security,
size: 35.0,
),
),
),
)
],
),
),
);
}
}

代码很简单,我们在ExpansionPanelList中仅仅放置了一个元素ExpansionPanel,对ExpansionPanel分别设置了它的标题和内容。

注意:ExpansionPanelList必须配合可以滑动的组件才可以使用

效果如下:

但是这个时候无论我们怎么点击右侧的图标都没有任何的反应,那是因为这个ExpansionPanel我们默认设置的是关闭的状态,而且我们也并没有对ExpansionPanelList的点击事件做处理。
下面简单改动下代码:

在ExpansionPanelListDemoState中增加如下代码:

1
2
3
4
5
6
7
8
9
10

var isExpanded;

_expansionCallback(index ,isExpanded){
setState(() {
if(this.isExpanded==isExpanded){
this.isExpanded=! this.isExpanded;
}
});
}

并且将isExpanded的值设置给ExpansionPanel的isExpanded属性。
接下来再来看下效果:

嗯,就是这个效果,可以看到在点击右侧按钮的同时下面body的展开时有动画的哦。
接下来我们试试多个ExpansionPanel的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

import 'package:flutter/material.dart';

void main() {
runApp(new MaterialApp(
home: ExpansionPanelListDemo(),
));
}

class ExpansionPanelListDemo extends StatefulWidget {
@override
_ExpansionPanelListDemoState createState() => _ExpansionPanelListDemoState();
}

class ExpandStateBean{
var isOpen;
var index;
ExpandStateBean(this.index,this.isOpen);
}

class _ExpansionPanelListDemoState extends State<ExpansionPanelListDemo> {
var currentPanelIndex = -1;
List<int> mList;
List<ExpandStateBean> expandStateList;
_ExpansionPanelListDemoState() {
mList = new List();
expandStateList=new List();
for (int i = 0; i < 10; i++) {
mList.add(i);
expandStateList.add(ExpandStateBean(i, false));
}
}


_setCurrentIndex(int index,isExpand) {
setState(() {
expandStateList.forEach((item){
if (item.index==index) {
item.isOpen=!isExpand;
}
});

});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanelList"),
),
body: SingleChildScrollView(child: ExpansionPanelList(
children: mList.map((index) {
return new ExpansionPanel(
headerBuilder: (context, isExpanded) {
return new ListTile(
title: new Text('我是第$index个标题'),
);
},
body: new Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: Container(height: 100.0,
color: Colors.blue,
alignment: Alignment.center,
child:Icon(Icons.security,size: 35.0,),),
),
isExpanded: expandStateList[index].isOpen,
);
}).toList(),

expansionCallback: (index, bol) {
_setCurrentIndex(index,bol);
},

),));
}
}

代码虽然有一点多,但是还是非常的简单的,因为是多个ExpansionPanel,所以我们要记录每个Item打开和关闭的状态来做处理,其他和上面的基本一致。

看下效果:

看第一种做法,使用ExpansionPanelList.radio()来实现,看名字就很容易知道,radio单选的意思嘛,也就是说每次只能打开一个ExpansionPanelRadio,只要ExpansionPanelList有已经打开的ExpansionPanelRadio就无法再打开其他的ExpansionPanelRadio。

用法跟ExpansionPanelList类似,只是把children替换成了ExpansionPanelRadio而已,不再做具体的介绍了,看代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

import 'package:flutter/material.dart';

void main() {
runApp(new MaterialApp(
home: ExpansionPanelListDemo(),
));
}

class ExpansionPanelListDemo extends StatefulWidget {
@override
_ExpansionPanelListDemoState createState() => _ExpansionPanelListDemoState();
}

class _ExpansionPanelListDemoState extends State<ExpansionPanelListDemo> {
var currentPanelIndex = -1;
List<int> mList;

_ExpansionPanelListDemoState() {
mList = new List();
for (int i = 0; i < 10; i++) {
mList.add(i);
}
}


_setCurrentIndex(int index) {
setState(() {

if(currentPanelIndex==index){
index=-1;
}
currentPanelIndex = index;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanelList"),
),
body: SingleChildScrollView(child: ExpansionPanelList.radio(
children: mList.map((index) {
return new ExpansionPanelRadio(
headerBuilder: (context, isExpanded) {
return new ListTile(
title: new Text('我是第$index个标题'),
);
},
body: new Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: Container(height: 100.0,
color: Colors.blue,
alignment: Alignment.center,
child:Icon(Icons.security,size: 35.0,),),
),
value: index,
);
}).toList(),
initialOpenPanelValue:currentPanelIndex ,
expansionCallback: (index, bol) {
_setCurrentIndex(index);
},

),));
}
}

效果如下:

实现起来还是非常的简单的,但是大家可能会发现一个问题,当有一个ExpansionPanelRadio打开时我们就没办法再去打开其他的ExpansionPanelRadio,除非先关闭这个打开的ExpansionPanelRadio。
那么如果你有这样的需求就还是要借助于ExpansionPanelList()了。

小结

  • 使用ExpansionPanelList可以实现带动画的展开布局效果
  • ExpansionPanelList中的ExpansionPanel是需要受ExpansionPanelList的点击事件处理的
  • 使用ExpansionPanelList.radio()限制每次只能有一个打开的Item

    试一试

在前面已经提到了,使用ExpansionPanelList.radio()每次只能打开一个Item,当有一个item处于打开状态时在点击其他item就没有效果了,但是我想要当我点击其他Item关于之前的Item打开现在Item如何做呢?

试一试吧

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

打赏请备注姓名或者昵称,方便我后期统计哦

关注公众号,及时查阅最新文章