【问题标题】:Is there a way in Flutter to allow only one ExpansionTile of a dynamic generated ListView to be expanded?Flutter 中有没有办法只允许扩展动态生成的 ListView 的一个 ExpansionTile?
【发布时间】:2020-02-01 15:36:44
【问题描述】:

Flutter 中有没有办法只允许动态生成的 ListView 的一个 ExpansionTile 展开?

例如我的 ListView 有三个 ExpansionTiles,我单击第一个,它会展开。现在,如果我点击第二个,第二个应该会展开,而第一个应该会自行关闭。

在我看来,它应该像将这个任务放入“onExpansionChanged”方法一样工作,但我不知道如何。

【问题讨论】:

    标签: flutter


    【解决方案1】:

    你不能使用 Flutter 的 ExpansionTile,但我创建了一个 CustomExpansionTile 允许这样做:

    CustomExpansionTile:

    
    import 'package:flutter/material.dart';
    
    const Duration _kExpand = Duration(milliseconds: 200);
    
    class CustomExpansionTile extends StatefulWidget {
    
      const CustomExpansionTile({
        Key key,
        this.leading,
        @required this.title,
        this.backgroundColor,
        this.children = const <Widget>[],
        this.trailing,
        @required this.expandedItem,
      }) :  super(key: key);
    
      /// A widget to display before the title.
      ///
      /// Typically a [CircleAvatar] widget.
      final Widget leading;
    
      /// The primary content of the list item.
      ///
      /// Typically a [Text] widget.
      final Widget title;
    
      /// The widgets that are displayed when the tile expands.
      ///
      /// Typically [ListTile] widgets.
      final List<Widget> children;
    
      /// The color to display behind the sublist when expanded.
      final Color backgroundColor;
    
      /// A widget to display instead of a rotating arrow icon.
      final Widget trailing;
    
      final ValueNotifier<Key> expandedItem;
    
      @override
      _CustomExpansionTileState createState() => _CustomExpansionTileState();
    }
    
    class _CustomExpansionTileState extends State<CustomExpansionTile> with SingleTickerProviderStateMixin {
      static final Animatable<double> _easeOutTween = CurveTween(curve: Curves.easeOut);
      static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
      static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
    
      final ColorTween _borderColorTween = ColorTween();
      final ColorTween _headerColorTween = ColorTween();
      final ColorTween _iconColorTween = ColorTween();
      final ColorTween _backgroundColorTween = ColorTween();
    
      AnimationController _controller;
      Animation<double> _iconTurns;
      Animation<double> _heightFactor;
      Animation<Color> _borderColor;
      Animation<Color> _headerColor;
      Animation<Color> _iconColor;
      Animation<Color> _backgroundColor;
    
      bool _isExpanded = false;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(duration: _kExpand, vsync: this);
        _heightFactor = _controller.drive(_easeInTween);
        _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
        _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
        _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
        _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
        _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
    
        _isExpanded =  widget.expandedItem.value == widget.key;
        if (_isExpanded)
          _controller.value = 1.0;
    
        widget.expandedItem.addListener(listener);
      }
    
      void listener() {
        setState(() {
          _changeState(widget.expandedItem.value == widget.key);
        });
      }
    
      @override
      void dispose() {
        _controller.dispose();
        widget.expandedItem.removeListener(listener);
        super.dispose();
      }
    
      void _changeState(bool isExpanded) {
        setState(() {
          _isExpanded = isExpanded;
          if (_isExpanded) {
            _controller.forward();
          } else {
            _controller.reverse().then<void>((void value) {
              if (!mounted)
                return;
              setState(() {
                // Rebuild without widget.children.
              });
            });
          }
          PageStorage.of(context)?.writeState(context, _isExpanded);
        });
      }
    
      void _handleTap() {
        _changeState(!_isExpanded);
        widget.expandedItem.value = _isExpanded ? widget.key : null;
      }
    
      Widget _buildChildren(BuildContext context, Widget child) {
        final Color borderSideColor = _borderColor.value ?? Colors.transparent;
    
        return Container(
          decoration: BoxDecoration(
            color: _backgroundColor.value ?? Colors.transparent,
            border: Border(
              top: BorderSide(color: borderSideColor),
              bottom: BorderSide(color: borderSideColor),
            ),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              ListTileTheme.merge(
                iconColor: _iconColor.value,
                textColor: _headerColor.value,
                child: ListTile(
                  onTap: _handleTap,
                  leading: widget.leading,
                  title: widget.title,
                  trailing: widget.trailing ?? RotationTransition(
                    turns: _iconTurns,
                    child: const Icon(Icons.expand_more),
                  ),
                ),
              ),
              ClipRect(
                child: Align(
                  heightFactor: _heightFactor.value,
                  child: child,
                ),
              ),
            ],
          ),
        );
      }
    
      @override
      void didChangeDependencies() {
        final ThemeData theme = Theme.of(context);
        _borderColorTween
          ..end = theme.dividerColor;
        _headerColorTween
          ..begin = theme.textTheme.subhead.color
          ..end = theme.accentColor;
        _iconColorTween
          ..begin = theme.unselectedWidgetColor
          ..end = theme.accentColor;
        _backgroundColorTween
          ..end = widget.backgroundColor;
        super.didChangeDependencies();
      }
    
      @override
      Widget build(BuildContext context) {
        final bool closed = !_isExpanded && _controller.isDismissed;
        return AnimatedBuilder(
          animation: _controller.view,
          builder: _buildChildren,
          child: closed ? null : Column(children: widget.children),
        );
    
      }
    
    }
    
    
    

    用法:

    
    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:playground/custom_expansion_tile.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    
    
    class _MyHomePageState extends State<MyHomePage> {
      ValueNotifier<Key> _expanded = ValueNotifier(null);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: ListView(
            children: <Widget>[
              CustomExpansionTile(
                expandedItem: _expanded,
                key: Key('1'),
                title: Text('Title #1'),
                children: <Widget>[
                  Text('Child')
                ],
              ),
              CustomExpansionTile(
                expandedItem: _expanded,
                key: Key('2'),
                title: Text('Title #2'),
                children: <Widget>[
                  Text('Child')
                ],
    
              ),
              CustomExpansionTile(
                expandedItem: _expanded,
                key: Key('3'),
                title: Text('Title #3'),
                children: <Widget>[
                  Text('Child')
                ],
    
              )
            ],
          ),
        );
      }
    }
    
    
    

    注意:您必须为 CustomExpansionTiles 提供唯一的密钥才能使其工作

    【讨论】:

    • 我尝试 ExpandablePanelList 来执行展开和折叠视图。它也有效
    • 非常感谢!
    • 你给了ValueNotifier&lt;Key&gt; _expanded = ValueNotifier(null); 什么,因为我在 NullSaftey 中遇到错误
    猜你喜欢
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    • 2019-03-08
    • 2013-01-08
    相关资源
    最近更新 更多