[Flutter Widget]ExpansionPanelList

前言


在前面的文章中我们介绍了可以展开的带标题控件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的用法吧

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

试一试


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

试一试吧

原文发布于微信公众号 - flutter开发者(Flutter_Developers)

原文发表时间:2018-10-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏nummy

使用jsTree树形控件【3】HTML结构

jsTree可以将无序列表转换成树形结构,最简单的HTML结构就是使用<ul>以及<li>,而且最好外面还嵌套了一个<div> $('#html1').jst...

1642
来自专栏BestSDK

“Excel格式”最风骚玩法,炫技加薪就靠它了

话不多说直接上干货! 001 自定义格式概述 01 调出单元格格式对话框 选中需要设置格式的单元格,按「CTRL+1」快捷键打开「设置单元格格式」对话框。 在对...

3623
来自专栏偏前端工程师的驿站

WebComponent魔法堂:深究Custom Element 之 标准构建

前言  通过《WebComponent魔法堂:深究Custom Element 之 面向痛点编程》,我们明白到其实Custom Element并不是什么新东西,...

20410
来自专栏進无尽的文章

实践-小细节 Ⅰ

     开发中总有一些细枝末节的东西是容易出错的地方,搜集总结下,避免再次掉入坑中。

872
来自专栏前端杂货铺

typeof的一些兼容性问题

typeof存在一些兼容性的问题,在IE6,7,8中的DOM和BOM元素及其对象上的方法的判定会出现误差,在safari上对NodeList实例 的判定,对Ex...

35215
来自专栏我的小碗汤

用go语言爬取珍爱网 | 第二回

昨天我们一起爬取珍爱网首页,拿到了城市列表页面,接下来在返回体城市列表中提取城市和url,即下图中的a标签里的href的值和innerText值。

1154
来自专栏我和未来有约会

用后台代码创建Storyboard

string storyboardName = "MyStoryBoard"; string myXamlElement = "MyXamlElement"...

2119
来自专栏Danny的专栏

【POI框架实战】——POI导出Excel时设置单元格类型为数值类型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

2233
来自专栏python学习路

五、XML与xpath--------------爬取美女图片 先用一个小实例开头吧(爬取贴吧每个帖子的图片)XML 和 HTML 的区别XML文档示例

除了正则表达式处理HTML文档,我们还可以用XPath,先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。 ----  先...

3654
来自专栏瓜大三哥

matlab GUI基础1

GUI编程开发 1.句柄图形 是一种面向对象的绘图系统。这些细节一般隐藏在图形M文件的内部,用户通过句柄图形可以定制图形的许多特性,这是使用高级绘图函数无法实现...

2848

扫码关注云+社区

领取腾讯云代金券