我一直在使用自定义的Expansion Tile小部件,以便能够以编程方式控制扩展:
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>((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),
);
}
}
现在,我可以将GlobalKey<AppExpansionTileState>
对象传递给此小部件以控制其扩展:
import 'package:flutter/material.dart';
void main() {
runApp(new ExpansionTileTest());
}
class ExpansionTileTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
final GlobalKey<AppExpansionTileState> key = GlobalKey();
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ExpansionTile Test'),
),
body: Column(
children: <Widget>[
FlatButton(
child: Text('Expand'),
onPressed: () => key.currentState.expand(),
),
AppExpansionTile(
key: key,
title: Text('My Tile'),
children: <Widget>[
Text('My Tile child'),
],
),
],
),
),
);
}
}
这是问题所在:在我的应用程序中,我非常慷慨地使用AppExpansionTile
,并且在某些地方它们相互嵌套。我一直在尝试为这些视图实现Expand All
和Collapse All
函数。当它们在同一级别上有多个时,很容易通过:
List<GlobalKey<AppExpansionTileState>> keys = List();
然后说:
keys.forEach((k) => k.currentState.expand());
我不太确定上面的方法是好的做法,但是它可以工作。但是,当它们彼此嵌套时必须扩展所有这些图块时,情况变得很复杂,并且GlobalKey
解决方案开始看起来很难看(示例得到简化,通常我使用{{1}创建图块}):
ListView.builder
有没有一种方法可以使用单个键来控制分配给它的多个小部件?
侧面说明:AppExpansionTile来自此帖子:https://stackoverflow.com/a/48935106/9488787。谢谢@西蒙。
答案 0 :(得分:0)
我通过为AppExpansionTile
创建一个控制器解决了这个问题:
class AppExpansionTileController extends ValueNotifier<bool> {
AppExpansionTileController(bool value) : super(value);
void expand() {
value = true;
}
void collapse() {
value = false;
}
void toggle() {
value = !value;
}
}
,然后将此控制器添加到我的AppExpansionTile
:
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.expansionController // Adding controller
})
: 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 AppExpansionTileController expansionController; // Adding controller
@override
AppExpansionTileState createState() => new AppExpansionTileState();
}
class AppExpansionTileState extends State<AppExpansionTile> with SingleTickerProviderStateMixin {
AnimationController _animationController;
CurvedAnimation _easeOutAnimation;
CurvedAnimation _easeInAnimation;
ColorTween _borderColor;
ColorTween _headerColor;
ColorTween _iconColor;
ColorTween _backgroundColor;
Animation<double> _iconTurns;
// Adding controller
AppExpansionTileController _expansionController;
AppExpansionTileController get _effectiveController => widget.controller ?? _expansionController;
bool _isExpanded;
@override
void initState() {
super.initState();
_animationController= new AnimationController(duration: _kExpand, vsync: this);
_easeOutAnimation = new CurvedAnimation(parent: _animationController, curve: Curves.easeOut);
_easeInAnimation = new CurvedAnimation(parent: _animationController, 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();
// If no controller was passed, we're creating our own controller with an initially collapsed state
if (widget.controller == null) _expansionController = AppExpansionTileController(false);
_isExpanded = _effectiveController.value;
//Listening to the value from our controller to change expansion state.
_effectiveController.addListener(() => _setExpanded(_effectiveController.value));
if (_isExpanded) _animationController.value = 1.0;
}
@override
void dispose() {
_animationController.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)
_animationController.forward();
else
_animationController.reverse().then<void>((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 && _animationController.isDismissed;
return new AnimatedBuilder(
animation: _animationController.view,
builder: _buildChildren,
child: closed ? null : new Column(children: widget.children),
);
}
}
现在,我可以创建控制器的单个实例,并使用它来控制所有AppExpansionTile
:
import 'package:flutter/material.dart';
void main() {
runApp(new ExpansionTileTest());
}
class ExpansionTileTest extends StatelessWidget {
//Creating a controller with an initially collapsed state
final AppExpansionTileController expansionController = AppExpansionTileController(false);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ExpansionTile Test'),
),
body: Column(
children: <Widget>[
FlatButton(
child: Text('Expand All'),
//Calling our controller's expand method
onPressed: () => expansionController.expand(),
),
AppExpansionTile(
expansionController: expansionController,
title: Text('My Tile 1'),
children: <Widget>[
AppExpansionTile(
expansionController: expansionController,
title: Text('My Child Tile 1'),
),
AppExpansionTile(
expansionController: expansionController,
title: Text('My Child Tile 2'),
),
],
),
AppExpansionTile(
expansionController: expansionController,
title: Text('My Tile 2'),
children: <Widget>[
AppExpansionTile(
expansionController: expansionController,
title: Text('My Child Tile 1'),
),
AppExpansionTile(
expansionController: expansionController,
title: Text('My Child Tile 2'),
),
],
),
],
),
),
);
}
}