// 修改了ExpansionPanelList的State的build方法 // 并删去了ExpansionPane(已经在原文件中定义),防止重定义 // 原版会在展开的项的上下添加及其难看的鸿沟 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 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 && 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]'; } } typedef ExpansionPanelCallback = void Function(int panelIndex, bool isExpanded); typedef ExpansionPanelHeaderBuilder = Widget Function( BuildContext context, bool isExpanded); /// 折叠列表 class ChznExpansionPanelList extends StatefulWidget { const ChznExpansionPanelList({ Key? key, this.children = const [], this.expansionCallback, this.animationDuration = kThemeAnimationDuration, this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding, this.dividerColor, this.elevation = 2, }) : assert(children != null), assert(animationDuration != null), _allowOnlyOnePanelOpen = false, initialOpenPanelValue = null, super(key: key); const ChznExpansionPanelList.radio({ Key? key, this.children = const [], this.expansionCallback, this.animationDuration = kThemeAnimationDuration, this.initialOpenPanelValue, this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding, this.dividerColor, this.elevation = 2, }) : assert(children != null), assert(animationDuration != null), _allowOnlyOnePanelOpen = true, super(key: key); final List children; final ExpansionPanelCallback? expansionCallback; final Duration animationDuration; final bool _allowOnlyOnePanelOpen; final Object? initialOpenPanelValue; final EdgeInsets expandedHeaderPadding; final Color? dividerColor; final double elevation; @override State createState() => _ExpansionPanelListState(); } class _ExpansionPanelListState extends State { 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(), widget.initialOpenPanelValue); } } } @override void didUpdateWidget(ChznExpansionPanelList oldWidget) { super.didUpdateWidget(oldWidget); if (widget._allowOnlyOnePanelOpen) { assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.'); if (!oldWidget._allowOnlyOnePanelOpen) { _currentOpenPanel = searchPanelByValue( widget.children.cast(), widget.initialOpenPanelValue); } } else { _currentOpenPanel = null; } } bool _allIdentifiersUnique() { final Map identifierMap = {}; for (final ExpansionPanelRadio child in widget.children.cast()) { 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 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 items = []; for (int index = 0; index < widget.children.length; index += 1) { // if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1)) // items.add(MaterialGap(key: _SaltedKey(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( 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: [ 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(context, index * 2), color: child.backgroundColor, child: Column( children: [ 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, ), ], ), ), ); } return MergeableMaterial( hasDividers: true, dividerColor: widget.dividerColor, elevation: widget.elevation.toInt(), children: items, ); } }