【问题标题】:Flutter- ExpansionTile expand and collapse on clickFlutter-ExpansionTile 在点击时展开和折叠
【发布时间】:2018-12-24 23:58:36
【问题描述】:

我将此作为扩展磁贴的扩展和折叠的参考--------Flutter - Collapsing ExpansionTile after choosing an item

我想要的是,如果我打开一个扩展磁贴并且用户单击另一个扩展磁贴应该关闭其他打开的扩展磁贴。

snapshot of my app

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

void main() {
  runApp(new ExpansionTileSample());
}

class ExpansionTileSample extends StatefulWidget {
  @override
  ExpansionTileSampleState createState() => new ExpansionTileSampleState();
}

class ExpansionTileSampleState extends State<ExpansionTileSample> {
  final GlobalKey<AppExpansionTileState> expansionTile = new GlobalKey();
  String foos = 'One';

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: const Text('ExpansionTile'),
        ),
        body: GestureDetector(
          onTap: () {
            setState(() {
             // this.foos = 'One';
              expansionTile.currentState.collapse();
            });
          },
          child: Column(
            children: <Widget>[
              new AppExpansionTile(
                  key: expansionTile,
                  title: new Text('1-3'),
                  backgroundColor:
                      Theme.of(context).accentColor.withOpacity(0.025),
                  children: <Widget>[
                    new ListTile(
                      title: const Text('One'),
                    ),
                    new ListTile(
                      title: const Text('Two'),
                    ),
                    new ListTile(
                      title: const Text('Three'),
                    ),
                  ]),
              new AppExpansionTile(
                  title: new Text('4-6'),
                  backgroundColor:
                      Theme.of(context).accentColor.withOpacity(0.025),
                  children: <Widget>[
                    new ListTile(
                      title: const Text('four'),
                    ),
                    new ListTile(
                      title: const Text('five'),
                    ),
                    new ListTile(
                      title: const Text('six'),
                    ),
                  ]),
              new AppExpansionTile(
                  title: new Text('6-9'),
                  backgroundColor:
                      Theme.of(context).accentColor.withOpacity(0.025),
                  children: <Widget>[
                    new ListTile(
                      title: const Text('seven'),
                    ),
                    new ListTile(
                      title: const Text('eight'),
                    ),
                    new ListTile(
                      title: const Text('nine'),
                    ),
                  ]),
            ],
          ),
        ),
      ),
    );
  }
}

// --- Copied and slightly modified version of the ExpansionTile.

const Duration _kExpand = const Duration(milliseconds: 200);

class AppExpansionTile extends StatefulWidget {
  const AppExpansionTile({
    Key key,
    this.leading,
    @required this.title,
    this.backgroundColor,
    this.onExpansionChanged,
    this.children: const <Widget>[],
    this.trailing,
    this.initiallyExpanded: false,
  })  : assert(initiallyExpanded != null),
        super(key: key);

  final Widget leading;
  final Widget title;
  final ValueChanged<bool> onExpansionChanged;
  final List<Widget> children;
  final Color backgroundColor;
  final Widget trailing;
  final bool initiallyExpanded;

  @override
  AppExpansionTileState createState() => new AppExpansionTileState();
}

class AppExpansionTileState extends State<AppExpansionTile>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  CurvedAnimation _easeOutAnimation;
  CurvedAnimation _easeInAnimation;
  ColorTween _borderColor;
  ColorTween _headerColor;
  ColorTween _iconColor;
  ColorTween _backgroundColor;
  Animation<double> _iconTurns;

  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(duration: _kExpand, vsync: this);
    _easeOutAnimation =
        new CurvedAnimation(parent: _controller, curve: Curves.easeOut);
    _easeInAnimation =
        new CurvedAnimation(parent: _controller, curve: Curves.easeIn);
    _borderColor = new ColorTween();
    _headerColor = new ColorTween();
    _iconColor = new ColorTween();
    _iconTurns =
        new Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation);
    _backgroundColor = new ColorTween();

    _isExpanded =
        PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
    if (_isExpanded) _controller.value = 1.0;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void expand() {
    _setExpanded(true);
  }

  void collapse() {
    _setExpanded(false);
  }

  void toggle() {
    _setExpanded(!_isExpanded);
  }

  void _setExpanded(bool isExpanded) {
    if (_isExpanded != isExpanded) {
      setState(() {
        _isExpanded = isExpanded;
        if (_isExpanded)
          _controller.forward();
        else
          _controller.reverse().then<void>((Null value) {
            setState(() {
              // Rebuild without widget.children.
            });
          });
        PageStorage.of(context)?.writeState(context, _isExpanded);
      });
      if (widget.onExpansionChanged != null) {
        widget.onExpansionChanged(_isExpanded);
      }
    }
  }

  Widget _buildChildren(BuildContext context, Widget child) {
    final Color borderSideColor =
        _borderColor.evaluate(_easeOutAnimation) ?? Colors.transparent;
    final Color titleColor = _headerColor.evaluate(_easeInAnimation);

    return new Container(
      decoration: new BoxDecoration(
          color: _backgroundColor.evaluate(_easeOutAnimation) ??
              Colors.transparent,
          border: new Border(
            top: new BorderSide(color: borderSideColor),
            bottom: new BorderSide(color: borderSideColor),
          )),
      child: new Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          IconTheme.merge(
            data:
                new IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
            child: new ListTile(
              onTap: toggle,
              leading: widget.leading,
              title: new DefaultTextStyle(
                style: Theme
                    .of(context)
                    .textTheme
                    .subhead
                    .copyWith(color: titleColor),
                child: widget.title,
              ),
              trailing: widget.trailing ??
                  new RotationTransition(
                    turns: _iconTurns,
                    child: const Icon(Icons.expand_more),
                  ),
            ),
          ),
          new ClipRect(
            child: new Align(
              heightFactor: _easeInAnimation.value,
              child: child,
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    _borderColor.end = theme.dividerColor;
    _headerColor
      ..begin = theme.textTheme.subhead.color
      ..end = theme.accentColor;
    _iconColor
      ..begin = theme.unselectedWidgetColor
      ..end = theme.accentColor;
    _backgroundColor.end = widget.backgroundColor;

    final bool closed = !_isExpanded && _controller.isDismissed;
    return new AnimatedBuilder(
      animation: _controller.view,
      builder: _buildChildren,
      child: closed ? null : new Column(children: widget.children),
    );
  }
}

【问题讨论】:

  • 我收到错误错误:无法将参数类型“(Null)→Null”分配给参数类型“(void)→动态”。 (argument_type_not_assignable 在 [capital_expansiontile] lib/main.dart:175)。 on _controller.reverse().then((Null value)
  • @Nick 去掉括号中的Null 解决错误
  • @alex blaze 您是否找到了使用扩展磁贴解决此问题的任何方法?

标签: dart flutter


【解决方案1】:

您可以使用 ExpandablePanelList 来执行展开和折叠视图。 使用expansionCallback 保持活动状态以执行展开或折叠。 这是工作示例

class TestExpandableView extends StatefulWidget {
  @override
  _TestExpandableViewState createState() => _TestExpandableViewState();
}

class _TestExpandableViewState extends State<TestExpandableView> {
  int _activeMeterIndex;
  @override
  Widget build(BuildContext context) {
    return Container(
      child: new ListView.builder(
          itemCount:  2,
          itemBuilder: (BuildContext context, int i) {
            return Card(
              margin:
              const EdgeInsets.fromLTRB(10.0, 15.0, 10.0, 0.0),
              child: new ExpansionPanelList(
                expansionCallback: (int index, bool status) {
                  setState(() {
                    _activeMeterIndex = _activeMeterIndex == i ? null : i;
                  });
                },
                children: [
                  new ExpansionPanel(
                      isExpanded: _activeMeterIndex == i,
                      headerBuilder: (BuildContext context,
                          bool isExpanded) =>
                      new Container(
                          padding:
                          const EdgeInsets.only(left: 15.0),
                          alignment: Alignment.centerLeft,
                          child: new Text(
                            'list-$i',
                          )),
                      body: new Container(child: new Text('content-$i'),),),
                ],
              ),
            );
          }),
    );
  }
}

【讨论】:

  • 您是否还注意到展开时每个 ExpansionPanel 顶部的额外边距?我们可以摆脱它吗?
  • 关于如何克服扩展时的额外边距的任何线索?
  • 只需将卡片转换为容器
  • 不必在expansionCallback 中保持活动状态,您可以使用ExpansionPanelList.radio,它接受ExpansionPanelRadio 的列表作为其子项。这将帮助您自动保持活动状态。参考flutter documentation for example usage
【解决方案2】:

不必像@Aravindh Kumar 那样在expansionCallback 中保持活动状态,您可以使用ExpansionPanelList.radio,它接受ExpansionPanelRadio 的列表作为其子项。此小部件最多允许打开列表中的一个面板,并自动为您保持活动状态。

请参阅flutter documentation for example usage

// Flutter code sample for ExpansionPanelList.radio

// Here is a simple example of how to implement ExpansionPanelList.radio.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: MyStatefulWidget(),
      ),
    );
  }
}

// stores ExpansionPanel state information
class Item {
  Item({
    this.id,
    this.expandedValue,
    this.headerValue,
  });

  int id;
  String expandedValue;
  String headerValue;
}

List<Item> generateItems(int numberOfItems) {
  return List.generate(numberOfItems, (int index) {
    return Item(
      id: index,
      headerValue: 'Panel $index',
      expandedValue: 'This is item number $index',
    );
  });
}

class MyStatefulWidget extends StatefulWidget {
  MyStatefulWidget({Key key}) : super(key: key);

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  List<Item> _data = generateItems(8);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Container(
        child: _buildPanel(),
      ),
    );
  }

  Widget _buildPanel() {
    return ExpansionPanelList.radio(
      initialOpenPanelValue: 2,
      children: _data.map<ExpansionPanelRadio>((Item item) {
        return ExpansionPanelRadio(
            value: item.id,
            headerBuilder: (BuildContext context, bool isExpanded) {
              return ListTile(
                title: Text(item.headerValue),
              );
            },
            body: ListTile(
                title: Text(item.expandedValue),
                subtitle: Text('To delete this panel, tap the trash can icon'),
                trailing: Icon(Icons.delete),
                onTap: () {
                  setState(() {
                    _data.removeWhere((currentItem) => item == currentItem);
                  });
                }));
      }).toList(),
    );
  }
}

【讨论】:

    【解决方案3】:

    我认为您需要单独跟踪每个扩展图块的扩展状态。比如:

    Map<String, bool> expansionState = Map();
    

    在初始化时,您需要指定键以及是否希望关联的扩展磁贴显示为打开或关闭。在我的代码中,我有一个扩展磁贴名称的列表。在页面加载时,我执行以下操作:

    categoryList.forEach((name) {
       expansionState.putIfAbsent(name, () => true);
    });
    

    那么builder就是简单的引用expansionState:

    Widget _buildCategory(String name, List<OrderItem> children) {
        return ExpansionTile(
          key: PageStorageKey<String>(name),
          initiallyExpanded: expansionState[name], // true,
          title: RichText(
              text: TextSpan(
            text: name,
            style: TextStyle(
                color: Colors.blue[800], fontSize: 16, fontWeight: FontWeight.bold),
            children: <TextSpan>[
              TextSpan(
                  text: ' - ' + children.length.toString() + ' items',
                  style: TextStyle(
                      color: Colors.grey[800],
                      fontSize: 16,
                      fontWeight: FontWeight.normal)),
            ],
          )),
          onExpansionChanged: ((newState) {
            print(name + ' is now ' + newState.toString());
            expansionState[name] = newState;
          }),
          children: children.map<Widget>(_buildChild).toList(),
        );
      }
    

    此时,您可以添加一个按钮,其onPressed() 方法可以在setState() 调用中以任何您想要的方式操作expansionState 映射。您可以将它们全部打开、全部关闭、切换它们的当前状态等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-12
      • 2016-02-02
      • 1970-01-01
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      • 2020-02-12
      • 2023-03-05
      相关资源
      最近更新 更多