在前面的文章中我们介绍了可以展开的带标题控件ExpansionTile的用法,在文章的最后还是按照惯例给大家留下了一个问题。
实现如下效果:
可以看到界面整体上是一个listView,在ListView的第二例是一个ExpansionTile,ExpansionTile的内部是多个ListTile,trailing结合自定义动画将“+”icon旋转22.5°变成了一个“×”,并且在ExpansionTile展开时改变了icon的颜色。
当然,代码实现起来也是非常的简单,不熟悉动画的童鞋可以看下公众号前面的文章.
代码:
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的用法吧
ExpansionPanel从单词的字面意思可以翻译为一个可以展开的面板,那么加上List就是包含多个可展开面板的列表啰。那么它又和前面讲过的ExpansionTile有什么区别,其实长得还是挺想的但是ExpansionPanelList在展开和关闭的时候是有动画的,比较不那么突兀。
ExpansionPanelList的构造方法:
代码:
ExpansionPanelList({
Key key,
this.children = const <ExpansionPanel>[],
this.expansionCallback,//展开关闭回调
this.animationDuration = kThemeAnimationDuration,//展开进行时间
})
构造方法很简单,接收ExpansionPanel类型的List集合,展开关闭的回调和展开时间三个参数。
ExpansionPanel的构造方法:
代码:
ExpansionPanel({
@required this.headerBuilder,//标题构造器
@required this.body,//内容区域
this.isExpanded = false//是否展开
})
构造方法非常的简单,但是在这里需要注意的是ExpansionPanel不是一个Widget它是一个单独类,只能配合ExpansionPanelList使用。
国际惯例,看下最基本的用法。
代码:
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中增加如下代码:
var isExpanded;
_expansionCallback(index ,isExpanded){
setState(() {
if(this.isExpanded==isExpanded){
this.isExpanded=! this.isExpanded;
}
});
}
并且将isExpanded的值设置给ExpansionPanel的isExpanded属性。
接下来再来看下效果:
嗯,就是这个效果,可以看到在点击右侧按钮的同时下面body的展开时有动画的哦。
接下来我们试试多个ExpansionPanel的效果
代码:
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而已,不再做具体的介绍了,看代码吧
代码:
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.radio()每次只能打开一个Item,当有一个item处于打开状态时在点击其他item就没有效果了,但是我想要当我点击其他Item关于之前的Item打开现在Item如何做呢?
试一试吧
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有