您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
343 行
13 KiB
343 行
13 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.widgets;
|
|
|
|
namespace Unity.UIWidgets.material {
|
|
static class ExpansionPanelUtils {
|
|
public const float _kPanelHeaderCollapsedHeight = 48.0f;
|
|
public const float _kPanelHeaderExpandedHeight = 64.0f;
|
|
}
|
|
|
|
|
|
class _SaltedKey<S, V> : LocalKey {
|
|
public _SaltedKey(
|
|
S salt,
|
|
V value) {
|
|
this.salt = salt;
|
|
this.value = value;
|
|
}
|
|
|
|
public readonly S salt;
|
|
|
|
public readonly V value;
|
|
|
|
public bool Equals(_SaltedKey<S, V> other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return other.salt.Equals(salt)
|
|
&& other.value.Equals(value);
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((_SaltedKey<S, V>) obj);
|
|
}
|
|
|
|
public static bool operator ==(_SaltedKey<S, V> left, _SaltedKey<S, V> right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(_SaltedKey<S, V> left, _SaltedKey<S, V> right) {
|
|
return !Equals(left, right);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = salt.GetHashCode();
|
|
hashCode = (hashCode * 397) ^ value.GetHashCode();
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
public override string ToString() {
|
|
string saltString = salt is string ? "<\'" + salt + "\'>" : "<" + salt + ">";
|
|
string valueString = value is string ? "<\'" + value + "\'>" : "<" + value + ">";
|
|
return "[" + saltString + " " + valueString + "]";
|
|
}
|
|
}
|
|
|
|
public delegate void ExpansionPanelCallback(int panelIndex, bool isExpanded);
|
|
|
|
public delegate Widget ExpansionPanelHeaderBuilder(BuildContext context, bool isExpanded);
|
|
|
|
|
|
public class ExpansionPanel {
|
|
public ExpansionPanel(
|
|
ExpansionPanelHeaderBuilder headerBuilder = null,
|
|
Widget body = null,
|
|
bool isExpanded = false,
|
|
bool canTapOnHeader = false) {
|
|
D.assert(headerBuilder != null);
|
|
D.assert(body != null);
|
|
this.headerBuilder = headerBuilder;
|
|
this.body = body;
|
|
this.isExpanded = isExpanded;
|
|
this.canTapOnHeader = false;
|
|
}
|
|
|
|
public readonly ExpansionPanelHeaderBuilder headerBuilder;
|
|
|
|
public readonly Widget body;
|
|
|
|
public readonly bool isExpanded;
|
|
|
|
public readonly bool canTapOnHeader;
|
|
}
|
|
|
|
|
|
public class ExpansionPanelRadio : ExpansionPanel {
|
|
public ExpansionPanelRadio(
|
|
object value = null,
|
|
ExpansionPanelHeaderBuilder headerBuilder = null,
|
|
Widget body = null,
|
|
bool canTapOnHeader = false)
|
|
: base(body: body, headerBuilder: headerBuilder, canTapOnHeader: canTapOnHeader) {
|
|
D.assert(headerBuilder != null);
|
|
D.assert(body != null);
|
|
D.assert(value != null);
|
|
this.value = value;
|
|
}
|
|
|
|
public readonly object value;
|
|
}
|
|
|
|
public class ExpansionPanelList : StatefulWidget {
|
|
public ExpansionPanelList(
|
|
Key key = null,
|
|
List<ExpansionPanel> children = null,
|
|
ExpansionPanelCallback expansionCallback = null,
|
|
TimeSpan? animationDuration = null) : base(key: key) {
|
|
this.children = children ?? new List<ExpansionPanel>();
|
|
this.expansionCallback = expansionCallback;
|
|
this.animationDuration = animationDuration ?? Constants.kThemeChangeDuration;
|
|
_allowOnlyOnePanelOpen = false;
|
|
initialOpenPanelValue = null;
|
|
}
|
|
|
|
ExpansionPanelList(
|
|
Key key = null,
|
|
List<ExpansionPanel> children = null,
|
|
ExpansionPanelCallback expansionCallback = null,
|
|
TimeSpan? animationDuration = null,
|
|
object initialOpenPanelValue = null) : base(key: key) {
|
|
this.children = children ?? new List<ExpansionPanel>();
|
|
this.expansionCallback = expansionCallback;
|
|
this.animationDuration = animationDuration ?? Constants.kThemeChangeDuration;
|
|
_allowOnlyOnePanelOpen = true;
|
|
this.initialOpenPanelValue = initialOpenPanelValue;
|
|
}
|
|
|
|
public static ExpansionPanelList radio(
|
|
Key key = null,
|
|
List<ExpansionPanelRadio> children = null,
|
|
ExpansionPanelCallback expansionCallback = null,
|
|
TimeSpan? animationDuration = null,
|
|
object initialOpenPanelValue = null) {
|
|
children = children ?? new List<ExpansionPanelRadio>();
|
|
var radio = new ExpansionPanelList(
|
|
key: key,
|
|
children: children.Cast<ExpansionPanel>().ToList(),
|
|
expansionCallback: expansionCallback,
|
|
animationDuration: animationDuration,
|
|
initialOpenPanelValue: initialOpenPanelValue
|
|
);
|
|
return radio;
|
|
}
|
|
|
|
public readonly List<ExpansionPanel> children;
|
|
|
|
public readonly ExpansionPanelCallback expansionCallback;
|
|
|
|
public readonly TimeSpan animationDuration;
|
|
|
|
public readonly bool _allowOnlyOnePanelOpen;
|
|
|
|
public readonly object initialOpenPanelValue;
|
|
|
|
public override State createState() {
|
|
return new _ExpansionPanelListState();
|
|
}
|
|
}
|
|
|
|
|
|
public class _ExpansionPanelListState : State<ExpansionPanelList> {
|
|
ExpansionPanelRadio _currentOpenPanel;
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
if (widget._allowOnlyOnePanelOpen) {
|
|
D.assert(_allIdentifierUnique(), () => "All object identifiers are not unique!");
|
|
foreach (ExpansionPanelRadio child in widget.children) {
|
|
if (widget.initialOpenPanelValue != null &&
|
|
child.value == widget.initialOpenPanelValue) {
|
|
_currentOpenPanel = child;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void didUpdateWidget(StatefulWidget oldWidget) {
|
|
base.didUpdateWidget(oldWidget);
|
|
ExpansionPanelList _oldWidget = (ExpansionPanelList) oldWidget;
|
|
if (widget._allowOnlyOnePanelOpen) {
|
|
D.assert(_allIdentifierUnique(), () => "All object identifiers are not unique!");
|
|
foreach (ExpansionPanelRadio newChild in widget.children) {
|
|
if (widget.initialOpenPanelValue != null &&
|
|
newChild.value == widget.initialOpenPanelValue) {
|
|
_currentOpenPanel = newChild;
|
|
}
|
|
}
|
|
}
|
|
else if (_oldWidget._allowOnlyOnePanelOpen) {
|
|
_currentOpenPanel = null;
|
|
}
|
|
}
|
|
|
|
bool _allIdentifierUnique() {
|
|
Dictionary<object, bool> identifierMap = new Dictionary<object, bool>();
|
|
foreach (ExpansionPanelRadio child in widget.children) {
|
|
identifierMap[child.value] = true;
|
|
}
|
|
|
|
return identifierMap.Count == widget.children.Count;
|
|
}
|
|
|
|
bool _isChildExpanded(int index) {
|
|
if (widget._allowOnlyOnePanelOpen) {
|
|
ExpansionPanelRadio radioWidget = (ExpansionPanelRadio) widget.children[index];
|
|
return _currentOpenPanel?.value == radioWidget.value;
|
|
}
|
|
|
|
return widget.children[index].isExpanded;
|
|
}
|
|
|
|
void _handlePressed(bool isExpanded, int index) {
|
|
if (widget.expansionCallback != null) {
|
|
widget.expansionCallback(index, isExpanded);
|
|
}
|
|
|
|
if (widget._allowOnlyOnePanelOpen) {
|
|
ExpansionPanelRadio pressedChild = (ExpansionPanelRadio) widget.children[index];
|
|
|
|
for (int childIndex = 0; childIndex < widget.children.Count; childIndex++) {
|
|
ExpansionPanelRadio child = (ExpansionPanelRadio) widget.children[childIndex];
|
|
if (widget.expansionCallback != null && childIndex != index &&
|
|
child.value == _currentOpenPanel?.value) {
|
|
widget.expansionCallback(childIndex, false);
|
|
}
|
|
}
|
|
|
|
_currentOpenPanel = isExpanded ? null : pressedChild;
|
|
}
|
|
|
|
setState(() => { });
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
List<MergeableMaterialItem> items = new List<MergeableMaterialItem>();
|
|
EdgeInsets kExpandedEdgeInsets = EdgeInsets.symmetric(
|
|
vertical: ExpansionPanelUtils._kPanelHeaderExpandedHeight -
|
|
ExpansionPanelUtils._kPanelHeaderCollapsedHeight);
|
|
|
|
for (int index = 0; index < widget.children.Count; index++) {
|
|
int expandIndex = index;
|
|
if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1)) {
|
|
items.Add(new MaterialGap(
|
|
key: new _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
|
|
}
|
|
|
|
ExpansionPanel child = widget.children[index];
|
|
Widget headerWidget = child.headerBuilder(
|
|
context,
|
|
_isChildExpanded(index)
|
|
);
|
|
Row header = new Row(
|
|
children: new List<Widget> {
|
|
new Expanded(
|
|
child: new AnimatedContainer(
|
|
duration: widget.animationDuration,
|
|
curve: Curves.fastOutSlowIn,
|
|
margin: _isChildExpanded(index) ? kExpandedEdgeInsets : EdgeInsets.zero,
|
|
child: new ConstrainedBox(
|
|
constraints: new BoxConstraints(
|
|
minHeight: ExpansionPanelUtils._kPanelHeaderCollapsedHeight),
|
|
child: headerWidget
|
|
)
|
|
)
|
|
),
|
|
new Container(
|
|
margin: EdgeInsets.fromLTRB(0, 0, 8, 0),
|
|
child: new ExpandIcon(
|
|
isExpanded: _isChildExpanded(index),
|
|
padding: EdgeInsets.all(16.0f),
|
|
onPressed: !child.canTapOnHeader
|
|
? (ValueChanged<bool>) ((bool isExpanded) => {
|
|
_handlePressed(isExpanded, expandIndex);
|
|
})
|
|
: null
|
|
)
|
|
)
|
|
}
|
|
);
|
|
|
|
items.Add(new MaterialSlice(
|
|
key: new _SaltedKey<BuildContext, int>(context, index * 2),
|
|
child: new Column(
|
|
children: new List<Widget> {
|
|
child.canTapOnHeader
|
|
? (Widget) new InkWell(
|
|
onTap: () =>
|
|
_handlePressed(_isChildExpanded(expandIndex), expandIndex),
|
|
child: header
|
|
)
|
|
: header,
|
|
new AnimatedCrossFade(
|
|
firstChild: new Container(height: 0.0f),
|
|
secondChild: child.body,
|
|
firstCurve: new Interval(0.0f, 0.6f, curve: Curves.fastOutSlowIn),
|
|
secondCurve: new Interval(0.4f, 1.0f, curve: Curves.fastOutSlowIn),
|
|
sizeCurve: Curves.fastOutSlowIn,
|
|
crossFadeState: _isChildExpanded(index)
|
|
? CrossFadeState.showSecond
|
|
: CrossFadeState.showFirst,
|
|
duration: widget.animationDuration
|
|
)
|
|
}
|
|
)
|
|
)
|
|
);
|
|
|
|
if (_isChildExpanded(index) && index != widget.children.Count - 1) {
|
|
items.Add(new MaterialGap(
|
|
key: new _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
|
|
}
|
|
}
|
|
|
|
return new MergeableMaterial(
|
|
hasDividers: true,
|
|
children: items);
|
|
}
|
|
}
|
|
}
|