Facebook
From Commodious Motmot, 3 Years ago, written in Dart.
Embed
Download Paste or View Raw
Hits: 571
  1. // Copyright 2014 The Flutter Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/widgets.dart';
  7.  
  8. const Duration _kExpand = Duration(milliseconds: 200);
  9.  
  10. /// A single-line [ListTile] with a trailing button that expands or collapses
  11. /// the tile to reveal or hide the [children].
  12. ///
  13. /// This widget is typically used with [ListView] to create an
  14. /// "expand / collapse" list entry. When used with scrolling widgets like
  15. /// [ListView], a unique [PageStorageKey] must be specified to enable the
  16. /// [ExpansionTile] to save and restore its expanded state when it is scrolled
  17. /// in and out of view.
  18. ///
  19. /// See also:
  20. ///
  21. ///  * [ListTile], useful for creating expansion tile [children] when the
  22. ///    expansion tile represents a sublist.
  23. ///  * The "Expand/collapse" section of
  24. ///    <https://material.io/guidelines/components/lists-controls.html>.
  25. class BetterExpansionTile extends StatefulWidget {
  26.   /// Creates a single-line [ListTile] with a trailing button that expands or collapses
  27.   /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must
  28.   /// be non-null.
  29.   const BetterExpansionTile({
  30.     Key key,
  31.     this.leading,
  32.     @required this.title,
  33.     this.subtitle,
  34.     this.backgroundColor,
  35.     this.onExpansionChanged,
  36.     this.children = const <Widget>[],
  37.     this.trailing,
  38.     this.initiallyExpanded = false,
  39.   }) : assert(initiallyExpanded != null),
  40.        super(key: key);
  41.  
  42.   /// A widget to display before the title.
  43.   ///
  44.   /// Typically a [CircleAvatar] widget.
  45.   final Widget leading;
  46.  
  47.   /// The primary content of the list item.
  48.   ///
  49.   /// Typically a [Text] widget.
  50.   final Widget title;
  51.  
  52.   /// Additional content displayed below the title.
  53.   ///
  54.   /// Typically a [Text] widget.
  55.   final Widget subtitle;
  56.  
  57.   /// Called when the tile expands or collapses.
  58.   ///
  59.   /// When the tile starts expanding, this function is called with the value
  60.   /// true. When the tile starts collapsing, this function is called with
  61.   /// the value false.
  62.   final ValueChanged<bool> onExpansionChanged;
  63.  
  64.   /// The widgets that are displayed when the tile expands.
  65.   ///
  66.   /// Typically [ListTile] widgets.
  67.   final List<Widget> children;
  68.  
  69.   /// The color to display behind the sublist when expanded.
  70.   final Color backgroundColor;
  71.  
  72.   /// A widget to display instead of a rotating arrow icon.
  73.   final Widget trailing;
  74.  
  75.   /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
  76.   final bool initiallyExpanded;
  77.  
  78.   @override
  79.   BetterExpansionTileState createState() => BetterExpansionTileState();
  80. }
  81.  
  82. class BetterExpansionTileState extends State<BetterExpansionTile> with SingleTickerProviderStateMixin {
  83.   static final Animatable<double> _easeOutTween = CurveTween(curve: Curves.easeOut);
  84.   static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
  85.   static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
  86.  
  87.   final ColorTween _borderColorTween = ColorTween();
  88.   final ColorTween _headerColorTween = ColorTween();
  89.   final ColorTween _iconColorTween = ColorTween();
  90.   final ColorTween _backgroundColorTween = ColorTween();
  91.  
  92.   AnimationController _controller;
  93.   Animation<double> _iconTurns;
  94.   Animation<double> _heightFactor;
  95.   Animation<Color> _borderColor;
  96.   Animation<Color> _headerColor;
  97.   Animation<Color> _iconColor;
  98.   Animation<Color> _backgroundColor;
  99.  
  100.   bool _isExpanded = false;
  101.  
  102.   @override
  103.   void initState() {
  104.     super.initState();
  105.     _controller = AnimationController(duration: _kExpand, vsync: this);
  106.     _heightFactor = _controller.drive(_easeInTween);
  107.     _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
  108.     _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
  109.     _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
  110.     _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
  111.     _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
  112.  
  113.     _isExpanded = PageStorage.of(context)?.readState(context) as bool ?? widget.initiallyExpanded;
  114.     if (_isExpanded)
  115.       _controller.value = 1.0;
  116.   }
  117.  
  118.   @override
  119.   void dispose() {
  120.     _controller.dispose();
  121.     super.dispose();
  122.   }
  123.  
  124.   void setExpanded(bool doExpand)
  125.   {
  126.     if ( doExpand != _isExpanded)
  127.       _handleTap();      
  128.   }
  129.  
  130.   void _handleTap() {
  131.     setState(() {
  132.       _isExpanded = !_isExpanded;
  133.       if (_isExpanded) {
  134.         _controller.forward();
  135.       } else {
  136.         _controller.reverse().then<void>((void value) {
  137.           if (!mounted)
  138.             return;
  139.           setState(() {
  140.             // Rebuild without widget.children.
  141.           });
  142.         });
  143.       }
  144.       PageStorage.of(context)?.writeState(context, _isExpanded);
  145.     });
  146.     if (widget.onExpansionChanged != null)
  147.       widget.onExpansionChanged(_isExpanded);
  148.   }
  149.  
  150.   Widget _buildChildren(BuildContext context, Widget child) {
  151.     final Color borderSideColor = _borderColor.value ?? Colors.transparent;
  152.  
  153.     return Container(
  154.       decoration: BoxDecoration(
  155.         color: _backgroundColor.value ?? Colors.transparent,
  156.         border: Border(
  157.           top: BorderSide(color: borderSideColor),
  158.           bottom: BorderSide(color: borderSideColor),
  159.         ),
  160.       ),
  161.       child: Column(
  162.         mainAxisSize: MainAxisSize.min,
  163.         children: <Widget>[
  164.           ListTileTheme.merge(
  165.             iconColor: _iconColor.value,
  166.             textColor: _headerColor.value,
  167.             child: ListTile(
  168.               onTap: _handleTap,
  169.               leading: widget.leading,
  170.               title: widget.title,
  171.               subtitle: widget.subtitle,
  172.               trailing: widget.trailing ?? RotationTransition(
  173.                 turns: _iconTurns,
  174.                 child: const Icon(Icons.expand_more),
  175.               ),
  176.             ),
  177.           ),
  178.           ClipRect(
  179.             child: Align(
  180.               heightFactor: _heightFactor.value,
  181.               child: child,
  182.             ),
  183.           ),
  184.         ],
  185.       ),
  186.     );
  187.   }
  188.  
  189.   @override
  190.   void didChangeDependencies() {
  191.     final ThemeData theme = Theme.of(context);
  192.     _borderColorTween.end = theme.dividerColor;
  193.     _headerColorTween
  194.       ..begin = theme.textTheme.subtitle1.color
  195.       ..end = theme.accentColor;
  196.     _iconColorTween
  197.       ..begin = theme.unselectedWidgetColor
  198.       ..end = theme.accentColor;
  199.     _backgroundColorTween.end = widget.backgroundColor;
  200.     super.didChangeDependencies();
  201.   }
  202.  
  203.   @override
  204.   Widget build(BuildContext context) {
  205.     final bool closed = !_isExpanded && _controller.isDismissed;
  206.     return AnimatedBuilder(
  207.       animation: _controller.view,
  208.       builder: _buildChildren,
  209.       child: closed ? null : Column(children: widget.children),
  210.     );
  211.  
  212.   }
  213. }