/* * @author lsy * @date 2020/5/20 **/ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; const Duration _kExpand = Duration(milliseconds: 200); class ExpansionLayout extends StatefulWidget { const ExpansionLayout({ Key key, this.leading, @required this.title, this.subtitle, 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 Widget subtitle; final ValueChanged<bool> onExpansionChanged; final List<Widget> children; final Color backgroundColor; final Widget trailing; final bool initiallyExpanded; @override _ExpansionTileState createState() => _ExpansionTileState(); } class _ExpansionTileState extends State<ExpansionLayout> 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 _backgroundColorTween = ColorTween(); AnimationController _controller; Animation<double> _iconTurns; Animation<double> _heightFactor; Animation<Color> _borderColor; Animation<Color> _headerColor; 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)); _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); _isExpanded = PageStorage.of(context)?.readState(context) as bool ?? widget.initiallyExpanded; if (_isExpanded) _controller.value = 1.0; } @override void dispose() { _controller.dispose(); super.dispose(); } void _handleTap() { 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); }); if (widget.onExpansionChanged != null) widget.onExpansionChanged(_isExpanded); } Widget _buildChildren(BuildContext context, Widget child) { final Color borderSideColor = _borderColor.value?? Color(0xffe5e5e5); return Container( decoration: BoxDecoration( // color: _backgroundColor.value ?? Colors.transparent, border: Border( bottom: BorderSide(color: borderSideColor), ), ), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ ListTileTheme.merge( contentPadding: EdgeInsets.only(), textColor: _headerColor.value, child: ListTile( onTap: _handleTap, leading: widget.leading, title: widget.title, subtitle: widget.subtitle, trailing: widget.trailing ?? RotationTransition( turns: _iconTurns, child: Container( width: 15, height: 16, child: SvgPicture.asset("images/bottom_arrow_black.svg"), ), ), ), ), ClipRect( child: Align( heightFactor: _heightFactor.value, child: child, ), ), ], ), ); } @override void didChangeDependencies() { final ThemeData theme = Theme.of(context); _borderColorTween.begin=Color(0xffe5e5e5); _borderColorTween.end = Colors.transparent; _headerColorTween ..begin = theme.textTheme.subtitle1.color ..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), ); } }