【问题标题】:Changing Icon Color behind ListTile in an ExpansionPanelList.radio在 ExpansionPanelList.radio 中更改 ListTile 后面的图标颜色
【发布时间】:2021-08-23 13:45:00
【问题描述】:

在ExpansionPanelList的ListTile中,试验已经作为向下箭头给出,当ListTile展开时,箭头变为向上箭头。

但是,它的颜色很深。我添加了尾随:Icon(blah blah)。这只是在标题和默认尾随按钮之间添加了一个图标(向下箭头)。

如何更改默认尾随按钮的颜色,同时保持其在框展开时变成向上箭头的多功能性?

我尝试将 ListTile 包装在一个主题中,这是有人推荐的,但向下箭头的颜色没有改变,如下所示:

Widget _buildPanel() {
    return ExpansionPanelList.radio(
      initialOpenPanelValue: 0,
      children: _data.map<ExpansionPanelRadio>((Item item) {
        return ExpansionPanelRadio(
          backgroundColor: Colors.black,
          canTapOnHeader: true,
          value: item.id,
          headerBuilder: (BuildContext context, bool isExpanded) {
            return Theme(
              data: Theme.of(context)
                  .copyWith(unselectedWidgetColor: Colors.white),
              child: ListTile(
                title: Text(item.headerValue,
                  
...

更新的错误:

【问题讨论】:

    标签: list flutter colors expansion


    【解决方案1】:

    如果您使用的是ExpansionPanelList,则只能通过主题亮度更改ExpandIcon(带有箭头的小部件)的颜色,这是ExpandIcon内的代码摘录:

        switch(Theme.of(context).brightness) {
          case Brightness.light:
            return Colors.black54;
          case Brightness.dark:
            return Colors.white60;
        }
    

    此小部件还具有更改其展开和禁用颜色的属性,但不幸的是ExpansionPanelList 没有将这些自定义属性传递给ExpandIcon,因此您将转到我在上面粘贴的回退开关。

    一种解决方案可以是从框架中提取ExpansionPanelList 并将属性color,disabledColor,expandedColor, 添加到其构造函数中。我将类 ExpansionPanelList 重命名为 ColoredExpansionPanelList 并添加了之前的颜色属性以将它们传递给它的子类 ExpandIcon

    // The following code is the same that is found inside the Flutter framework 
    //just adding the `color,disabledColor,expandedColor` for personalizing the `ExpandedIcon`
    
    // Copyright 2014 The Flutter Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    import 'package:flutter/material.dart';
    import 'package:flutter/widgets.dart';
    
    
    
    const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
    const EdgeInsets _kPanelHeaderExpandedDefaultPadding = EdgeInsets.symmetric(
      vertical: 64.0 - _kPanelHeaderCollapsedHeight,
    );
    
    class _SaltedKey<S, V> extends LocalKey {
      const _SaltedKey(this.salt, this.value);
    
      final S salt;
      final V value;
    
      @override
      bool operator ==(Object other) {
        if (other.runtimeType != runtimeType)
          return false;
        return other is _SaltedKey<S, V>
            && other.salt == salt
            && other.value == value;
      }
    
      @override
      int get hashCode => hashValues(runtimeType, salt, value);
    
      @override
      String toString() {
        final String saltString = S == String ? "<'$salt'>" : '<$salt>';
        final String valueString = V == String ? "<'$value'>" : '<$value>';
        return '[$saltString $valueString]';
      }
    }
    
    /// Signature for the callback that's called when an [ExpansionPanel] is
    /// expanded or collapsed.
    ///
    /// The position of the panel within an [ColoredExpansionPanelList] is given by
    /// [panelIndex].
    typedef ExpansionPanelCallback = void Function(int panelIndex, bool isExpanded);
    
    /// Signature for the callback that's called when the header of the
    /// [ExpansionPanel] needs to rebuild.
    typedef ExpansionPanelHeaderBuilder = Widget Function(BuildContext context, bool isExpanded);
    
    /// A material expansion panel. It has a header and a body and can be either
    /// expanded or collapsed. The body of the panel is only visible when it is
    /// expanded.
    ///
    /// Expansion panels are only intended to be used as children for
    /// [ColoredExpansionPanelList].
    ///
    /// See [ColoredExpansionPanelList] for a sample implementation.
    ///
    /// See also:
    ///
    ///  * [ColoredExpansionPanelList]
    ///  * <https://material.io/design/components/lists.html#types>
    class ExpansionPanel {
      /// Creates an expansion panel to be used as a child for [ColoredExpansionPanelList].
      /// See [ColoredExpansionPanelList] for an example on how to use this widget.
      ///
      /// The [headerBuilder], [body], and [isExpanded] arguments must not be null.
      ExpansionPanel({
        required this.headerBuilder,
        required this.body,
        this.isExpanded = false,
        this.canTapOnHeader = false,
        this.backgroundColor,
      }) : assert(headerBuilder != null),
            assert(body != null),
            assert(isExpanded != null),
            assert(canTapOnHeader != null);
    
      /// The widget builder that builds the expansion panels' header.
      final ExpansionPanelHeaderBuilder headerBuilder;
    
      /// The body of the expansion panel that's displayed below the header.
      ///
      /// This widget is visible only when the panel is expanded.
      final Widget body;
    
      /// Whether the panel is expanded.
      ///
      /// Defaults to false.
      final bool isExpanded;
    
      /// Whether tapping on the panel's header will expand/collapse it.
      ///
      /// Defaults to false.
      final bool canTapOnHeader;
    
      /// Defines the background color of the panel.
      ///
      /// Defaults to [ThemeData.cardColor].
      final Color? backgroundColor;
    }
    
    /// An expansion panel that allows for radio-like functionality.
    /// This means that at any given time, at most, one [ExpansionPanelRadio]
    /// can remain expanded.
    ///
    /// A unique identifier [value] must be assigned to each panel.
    /// This identifier allows the [ColoredExpansionPanelList] to determine
    /// which [ExpansionPanelRadio] instance should be expanded.
    ///
    /// See [ExpansionPanelList.radio] for a sample implementation.
    class ExpansionPanelRadio extends ExpansionPanel {
    
      /// An expansion panel that allows for radio functionality.
      ///
      /// A unique [value] must be passed into the constructor. The
      /// [headerBuilder], [body], [value] must not be null.
      ExpansionPanelRadio({
        required this.value,
        required ExpansionPanelHeaderBuilder headerBuilder,
        required Widget body,
        bool canTapOnHeader = false,
        Color? backgroundColor,
      }) : assert(value != null),
            super(
            body: body,
            headerBuilder: headerBuilder,
            canTapOnHeader: canTapOnHeader,
            backgroundColor: backgroundColor,
          );
    
      /// The value that uniquely identifies a radio panel so that the currently
      /// selected radio panel can be identified.
      final Object value;
    }
    
    /// A material expansion panel list that lays out its children and animates
    /// expansions.
    ///
    /// Note that [expansionCallback] behaves differently for [ColoredExpansionPanelList]
    /// and [ExpansionPanelList.radio].
    ///
    /// {@tool dartpad --template=stateful_widget_scaffold}
    ///
    /// Here is a simple example of how to implement ExpansionPanelList.
    ///
    /// ```dart preamble
    /// // stores ExpansionPanel state information
    /// class Item {
    ///   Item({
    ///     required this.expandedValue,
    ///     required this.headerValue,
    ///     this.isExpanded = false,
    ///   });
    ///
    ///   String expandedValue;
    ///   String headerValue;
    ///   bool isExpanded;
    /// }
    ///
    /// List<Item> generateItems(int numberOfItems) {
    ///   return List<Item>.generate(numberOfItems, (int index) {
    ///     return Item(
    ///       headerValue: 'Panel $index',
    ///       expandedValue: 'This is item number $index',
    ///     );
    ///   });
    /// }
    /// ```
    ///
    /// ```dart
    /// final List<Item> _data = generateItems(8);
    ///
    /// @override
    /// Widget build(BuildContext context) {
    ///   return SingleChildScrollView(
    ///     child: Container(
    ///       child: _buildPanel(),
    ///     ),
    ///   );
    /// }
    ///
    /// Widget _buildPanel() {
    ///   return ExpansionPanelList(
    ///     expansionCallback: (int index, bool isExpanded) {
    ///       setState(() {
    ///         _data[index].isExpanded = !isExpanded;
    ///       });
    ///     },
    ///     children: _data.map<ExpansionPanel>((Item item) {
    ///       return ExpansionPanel(
    ///         headerBuilder: (BuildContext context, bool isExpanded) {
    ///           return ListTile(
    ///             title: Text(item.headerValue),
    ///           );
    ///         },
    ///         body: ListTile(
    ///           title: Text(item.expandedValue),
    ///           subtitle: const Text('To delete this panel, tap the trash can icon'),
    ///           trailing: const Icon(Icons.delete),
    ///           onTap: () {
    ///             setState(() {
    ///               _data.removeWhere((Item currentItem) => item == currentItem);
    ///             });
    ///           }
    ///         ),
    ///         isExpanded: item.isExpanded,
    ///       );
    ///     }).toList(),
    ///   );
    /// }
    /// ```
    /// {@end-tool}
    ///
    /// See also:
    ///
    ///  * [ExpansionPanel]
    ///  * [ExpansionPanelList.radio]
    ///  * <https://material.io/design/components/lists.html#types>
    class ColoredExpansionPanelList extends StatefulWidget {
      /// Creates an expansion panel list widget. The [expansionCallback] is
      /// triggered when an expansion panel expand/collapse button is pushed.
      ///
      /// The [children] and [animationDuration] arguments must not be null.
      const ColoredExpansionPanelList({
        Key? key,
        this.children = const <ExpansionPanel>[],
        this.expansionCallback,
        this.animationDuration = kThemeAnimationDuration,
        this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
        this.dividerColor,
        this.color,
        this.disabledColor,
        this.expandedColor,
        this.elevation = 2,
      }) : assert(children != null),
            assert(animationDuration != null),
            _allowOnlyOnePanelOpen = false,
            initialOpenPanelValue = null,
            super(key: key);
    
    
      /// START OF CUSTOM CODE TO ADD COLOR TO EXPANDEDICON
      /// 
      /// 
      /// 
      /// The color of the icon.
      ///
      /// Defaults to [Colors.black54] when the theme's
      /// [ThemeData.brightness] is [Brightness.light] and to
      /// [Colors.white60] when it is [Brightness.dark]. This adheres to the
      /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
      /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
      final Color? color;
    
      /// The color of the icon when it is disabled,
      /// i.e. if [onPressed] is null.
      ///
      /// Defaults to [Colors.black38] when the theme's
      /// [ThemeData.brightness] is [Brightness.light] and to
      /// [Colors.white38] when it is [Brightness.dark]. This adheres to the
      /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
      /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
      final Color? disabledColor;
    
      /// The color of the icon when the icon is expanded.
      ///
      /// Defaults to [Colors.black54] when the theme's
      /// [ThemeData.brightness] is [Brightness.light] and to
      /// [Colors.white] when it is [Brightness.dark]. This adheres to the
      /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
      /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
      final Color? expandedColor;
    
      ///END OF CUSTOM CODE TO ADD COLOR TO EXPANDEDICON
      /// 
      /// 
      /// 
      /// 
      /// 
    
    
      /// Creates a radio expansion panel list widget.
      ///
      /// This widget allows for at most one panel in the list to be open.
      /// The expansion panel callback is triggered when an expansion panel
      /// expand/collapse button is pushed. The [children] and [animationDuration]
      /// arguments must not be null. The [children] objects must be instances
      /// of [ExpansionPanelRadio].
      ///
      /// {@tool dartpad --template=stateful_widget_scaffold}
      ///
      /// Here is a simple example of how to implement ExpansionPanelList.radio.
      ///
      /// ```dart preamble
      /// // stores ExpansionPanel state information
      /// class Item {
      ///   Item({
      ///     required this.id,
      ///     required this.expandedValue,
      ///     required this.headerValue,
      ///   });
      ///
      ///   int id;
      ///   String expandedValue;
      ///   String headerValue;
      /// }
      ///
      /// List<Item> generateItems(int numberOfItems) {
      ///   return List<Item>.generate(numberOfItems, (int index) {
      ///     return Item(
      ///       id: index,
      ///       headerValue: 'Panel $index',
      ///       expandedValue: 'This is item number $index',
      ///     );
      ///   });
      /// }
      /// ```
      ///
      /// ```dart
      /// final 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: const Text('To delete this panel, tap the trash can icon'),
      ///           trailing: const Icon(Icons.delete),
      ///           onTap: () {
      ///             setState(() {
      ///               _data.removeWhere((Item currentItem) => item == currentItem);
      ///             });
      ///           }
      ///         )
      ///       );
      ///     }).toList(),
      ///   );
      /// }
      /// ```
      /// {@end-tool}
      const ColoredExpansionPanelList.radio({
        Key? key,
        this.children = const <ExpansionPanelRadio>[],
        this.expansionCallback,
        this.animationDuration = kThemeAnimationDuration,
        this.initialOpenPanelValue,
        this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
        this.dividerColor,
        this.color,
        this.disabledColor,
        this.expandedColor,
        this.elevation = 2,
      }) : assert(children != null),
            assert(animationDuration != null),
            _allowOnlyOnePanelOpen = true,
            super(key: key);
    
      /// The children of the expansion panel list. They are laid out in a similar
      /// fashion to [ListBody].
      final List<ExpansionPanel> children;
    
      /// The callback that gets called whenever one of the expand/collapse buttons
      /// is pressed. The arguments passed to the callback are the index of the
      /// pressed panel and whether the panel is currently expanded or not.
      ///
      /// If ExpansionPanelList.radio is used, the callback may be called a
      /// second time if a different panel was previously open. The arguments
      /// passed to the second callback are the index of the panel that will close
      /// and false, marking that it will be closed.
      ///
      /// For ExpansionPanelList, the callback needs to setState when it's notified
      /// about the closing/opening panel. On the other hand, the callback for
      /// ExpansionPanelList.radio is simply meant to inform the parent widget of
      /// changes, as the radio panels' open/close states are managed internally.
      ///
      /// This callback is useful in order to keep track of the expanded/collapsed
      /// panels in a parent widget that may need to react to these changes.
      final ExpansionPanelCallback? expansionCallback;
    
      /// The duration of the expansion animation.
      final Duration animationDuration;
    
      // Whether multiple panels can be open simultaneously
      final bool _allowOnlyOnePanelOpen;
    
      /// The value of the panel that initially begins open. (This value is
      /// only used when initializing with the [ExpansionPanelList.radio]
      /// constructor.)
      final Object? initialOpenPanelValue;
    
      /// The padding that surrounds the panel header when expanded.
      ///
      /// By default, 16px of space is added to the header vertically (above and below)
      /// during expansion.
      final EdgeInsets expandedHeaderPadding;
    
      /// Defines color for the divider when [ExpansionPanel.isExpanded] is false.
      ///
      /// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
      /// is null, then [ThemeData.dividerColor] is used.
      final Color? dividerColor;
    
      /// Defines elevation for the [ExpansionPanel] while it's expanded.
      ///
      /// This uses [kElevationToShadow] to simulate shadows, it does not use
      /// [Material]'s arbitrary elevation feature.
      ///
      /// The following values can be used to define the elevation: 0, 1, 2, 3, 4, 6,
      /// 8, 9, 12, 16, 24.
      ///
      /// By default, the value of elevation is 2.
      final int elevation;
    
      @override
      State<StatefulWidget> createState() => _ColoredExpansionPanelListState();
    }
    
    class _ColoredExpansionPanelListState extends State<ColoredExpansionPanelList> {
      ExpansionPanelRadio? _currentOpenPanel;
    
      @override
      void initState() {
        super.initState();
        if (widget._allowOnlyOnePanelOpen) {
          assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
          if (widget.initialOpenPanelValue != null) {
            _currentOpenPanel =
                searchPanelByValue(widget.children.cast<ExpansionPanelRadio>(), widget.initialOpenPanelValue);
          }
        }
      }
    
      @override
      void didUpdateWidget(ColoredExpansionPanelList oldWidget) {
        super.didUpdateWidget(oldWidget);
    
        if (widget._allowOnlyOnePanelOpen) {
          assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
          // If the previous widget was non-radio ExpansionPanelList, initialize the
          // open panel to widget.initialOpenPanelValue
          if (!oldWidget._allowOnlyOnePanelOpen) {
            _currentOpenPanel =
                searchPanelByValue(widget.children.cast<ExpansionPanelRadio>(), widget.initialOpenPanelValue);
          }
        } else {
          _currentOpenPanel = null;
        }
      }
    
      bool _allIdentifiersUnique() {
        final Map<Object, bool> identifierMap = <Object, bool>{};
        for (final ExpansionPanelRadio child in widget.children.cast<ExpansionPanelRadio>()) {
          identifierMap[child.value] = true;
        }
        return identifierMap.length == widget.children.length;
      }
    
      bool _isChildExpanded(int index) {
        if (widget._allowOnlyOnePanelOpen) {
          final ExpansionPanelRadio radioWidget = widget.children[index] as ExpansionPanelRadio;
          return _currentOpenPanel?.value == radioWidget.value;
        }
        return widget.children[index].isExpanded;
      }
    
      void _handlePressed(bool isExpanded, int index) {
        widget.expansionCallback?.call(index, isExpanded);
    
        if (widget._allowOnlyOnePanelOpen) {
          final ExpansionPanelRadio pressedChild = widget.children[index] as ExpansionPanelRadio;
    
          // If another ExpansionPanelRadio was already open, apply its
          // expansionCallback (if any) to false, because it's closing.
          for (int childIndex = 0; childIndex < widget.children.length; childIndex += 1) {
            final ExpansionPanelRadio child = widget.children[childIndex] as ExpansionPanelRadio;
            if (widget.expansionCallback != null &&
                childIndex != index &&
                child.value == _currentOpenPanel?.value)
              widget.expansionCallback!(childIndex, false);
          }
    
          setState(() {
            _currentOpenPanel = isExpanded ? null : pressedChild;
          });
        }
      }
    
      ExpansionPanelRadio? searchPanelByValue(List<ExpansionPanelRadio> panels, Object? value)  {
        for (final ExpansionPanelRadio panel in panels) {
          if (panel.value == value)
            return panel;
        }
        return null;
      }
    
      @override
      Widget build(BuildContext context) {
        assert(kElevationToShadow.containsKey(widget.elevation),
        'Invalid value for elevation. See the kElevationToShadow constant for'
            ' possible elevation values.',
        );
    
        final List<MergeableMaterialItem> items = <MergeableMaterialItem>[];
    
        for (int index = 0; index < widget.children.length; index += 1) {
          if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
            items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
    
          final ExpansionPanel child = widget.children[index];
          final Widget headerWidget = child.headerBuilder(
            context,
            _isChildExpanded(index),
          );
    
          Widget expandIconContainer = Container(
            margin: const EdgeInsetsDirectional.only(end: 8.0),
            child: ExpandIcon(
              color: widget.color,
              disabledColor: widget.disabledColor,
              expandedColor:  widget.expandedColor,
              isExpanded: _isChildExpanded(index),
              padding: const EdgeInsets.all(16.0),
              onPressed: !child.canTapOnHeader
                  ? (bool isExpanded) => _handlePressed(isExpanded, index)
                  : null,
            ),
          );
          if (!child.canTapOnHeader) {
            final MaterialLocalizations localizations = MaterialLocalizations.of(context);
            expandIconContainer = Semantics(
              label: _isChildExpanded(index)? localizations.expandedIconTapHint : localizations.collapsedIconTapHint,
              container: true,
              child: expandIconContainer,
            );
          }
          Widget header = Row(
            children: <Widget>[
              Expanded(
                child: AnimatedContainer(
                  duration: widget.animationDuration,
                  curve: Curves.fastOutSlowIn,
                  margin: _isChildExpanded(index) ? widget.expandedHeaderPadding : EdgeInsets.zero,
                  child: ConstrainedBox(
                    constraints: const BoxConstraints(minHeight: _kPanelHeaderCollapsedHeight),
                    child: headerWidget,
                  ),
                ),
              ),
              expandIconContainer,
            ],
          );
          if (child.canTapOnHeader) {
            header = MergeSemantics(
              child: InkWell(
                onTap: () => _handlePressed(_isChildExpanded(index), index),
                child: header,
              ),
            );
          }
          items.add(
            MaterialSlice(
              key: _SaltedKey<BuildContext, int>(context, index * 2),
              color: child.backgroundColor,
              child: Column(
                children: <Widget>[
                  header,
                  AnimatedCrossFade(
                    firstChild: Container(height: 0.0),
                    secondChild: child.body,
                    firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
                    secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
                    sizeCurve: Curves.fastOutSlowIn,
                    crossFadeState: _isChildExpanded(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst,
                    duration: widget.animationDuration,
                  ),
                ],
              ),
            ),
          );
    
          if (_isChildExpanded(index) && index != widget.children.length - 1)
            items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
        }
    
        return MergeableMaterial(
          hasDividers: true,
          dividerColor: widget.dividerColor,
          elevation: widget.elevation,
          children: items,
        );
      }
    }
    
    

    Original ExpansionPanelList code

    【讨论】:

    • 嘿,谢谢!我运行了您的代码,但出现了错误。它没有出现在你身边吗?我附上了问题中错误的屏幕截图
    • 嘿@VegetaSaiyan 你是对的我错过了一些属性,刚刚更新了代码。它现在应该可以工作了?
    • 所有的好兄弟,认为这很简单。非常感谢!现在可以了。但是缺少的是什么? :D
    • 不是你赛亚人王子,我错过了.radio工厂构造函数中的新参数。并且 alos 错误地使用 this.color 而不是 widget.color 从状态访问它们
    • 我明白了。谢谢!你有领英吗?因为我正在启动一个应用程序,认为保持联系会很好。哈哈
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-08
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    相关资源
    最近更新 更多