在前面的文章中我们介绍了可以展开的带标题控件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如何做呢?
试一试吧
本文分享自微信公众号 - flutter开发者(Flutter_Developers),作者:Flutter开发者
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2018-10-10
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句