"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/material/navigation_rail.dart" (13 Nov 2020, 32043 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 // @dart = 2.8
    6 
    7 import 'dart:ui';
    8 
    9 import 'package:flutter/widgets.dart';
   10 
   11 import '../../scheduler.dart';
   12 
   13 import 'color_scheme.dart';
   14 import 'ink_well.dart';
   15 import 'material.dart';
   16 import 'material_localizations.dart';
   17 import 'navigation_rail_theme.dart';
   18 import 'theme.dart';
   19 import 'theme_data.dart';
   20 
   21 /// A material widget that is meant to be displayed at the left or right of an
   22 /// app to navigate between a small number of views, typically between three and
   23 /// five.
   24 ///
   25 /// The navigation rail is meant for layouts with wide viewports, such as a
   26 /// desktop web or tablet landscape layout. For smaller layouts, like mobile
   27 /// portrait, a [BottomNavigationBar] should be used instead.
   28 ///
   29 /// A navigation rail is usually used as the first or last element of a [Row]
   30 /// which defines the app's [Scaffold] body.
   31 ///
   32 /// The appearance of all of the [NavigationRail]s within an app can be
   33 /// specified with [NavigationRailTheme]. The default values for null theme
   34 /// properties are based on the [Theme]'s [ThemeData.textTheme],
   35 /// [ThemeData.iconTheme], and [ThemeData.colorScheme].
   36 ///
   37 /// Adaptive layouts can build different instances of the [Scaffold] in order to
   38 /// have a navigation rail for more horizontal layouts and a bottom navigation
   39 /// bar for more vertical layouts. See
   40 /// [https://github.com/flutter/samples/blob/master/experimental/web_dashboard/lib/src/widgets/third_party/adaptive_scaffold.dart]
   41 /// for an example.
   42 ///
   43 /// {@tool dartpad --template=stateful_widget_material}
   44 ///
   45 /// This example shows a [NavigationRail] used within a Scaffold with 3
   46 /// [NavigationRailDestination]s. The main content is separated by a divider
   47 /// (although elevation on the navigation rail can be used instead). The
   48 /// `_selectedIndex` is updated by the `onDestinationSelected` callback.
   49 ///
   50 /// ```dart
   51 /// int _selectedIndex = 0;
   52 ///
   53 ///  @override
   54 ///  Widget build(BuildContext context) {
   55 ///    return Scaffold(
   56 ///      body: Row(
   57 ///        children: <Widget>[
   58 ///          NavigationRail(
   59 ///            selectedIndex: _selectedIndex,
   60 ///            onDestinationSelected: (int index) {
   61 ///              setState(() {
   62 ///                _selectedIndex = index;
   63 ///              });
   64 ///            },
   65 ///            labelType: NavigationRailLabelType.selected,
   66 ///            destinations: [
   67 ///              NavigationRailDestination(
   68 ///                icon: Icon(Icons.favorite_border),
   69 ///                selectedIcon: Icon(Icons.favorite),
   70 ///                label: Text('First'),
   71 ///              ),
   72 ///              NavigationRailDestination(
   73 ///                icon: Icon(Icons.bookmark_border),
   74 ///                selectedIcon: Icon(Icons.book),
   75 ///                label: Text('Second'),
   76 ///              ),
   77 ///              NavigationRailDestination(
   78 ///                icon: Icon(Icons.star_border),
   79 ///                selectedIcon: Icon(Icons.star),
   80 ///                label: Text('Third'),
   81 ///              ),
   82 ///            ],
   83 ///          ),
   84 ///          VerticalDivider(thickness: 1, width: 1),
   85 ///          // This is the main content.
   86 ///          Expanded(
   87 ///            child: Center(
   88 ///              child: Text('selectedIndex: $_selectedIndex'),
   89 ///            ),
   90 ///          )
   91 ///        ],
   92 ///      ),
   93 ///    );
   94 ///  }
   95 /// ```
   96 /// {@end-tool}
   97 ///
   98 /// See also:
   99 ///
  100 ///  * [Scaffold], which can display the navigation rail within a [Row] of the
  101 ///    [Scaffold.body] slot.
  102 ///  * [NavigationRailDestination], which is used as a model to create tappable
  103 ///    destinations in the navigation rail.
  104 ///  * [BottomNavigationBar], which is a similar navigation widget that's laid
  105 ///     out horizontally.
  106 ///  * [https://material.io/components/navigation-rail/]
  107 class NavigationRail extends StatefulWidget {
  108   /// Creates a material design navigation rail.
  109   ///
  110   /// The value of [destinations] must be a list of one or more
  111   /// [NavigationRailDestination] values.
  112   ///
  113   /// If [elevation] is specified, it must be non-negative.
  114   ///
  115   /// If [minWidth] is specified, it must be non-negative, and if
  116   /// [minExtendedWidth] is specified, it must be non-negative and greater than
  117   /// [minWidth].
  118   ///
  119   /// The argument [extended] must not be null. [extended] can only be set to
  120   /// true when when the [labelType] is null or [NavigationRailLabelType.none].
  121   ///
  122   /// If [backgroundColor], [elevation], [groupAlignment], [labelType],
  123   /// [unselectedLabelTextStyle], [selectedLabelTextStyle],
  124   /// [unselectedIconTheme], or [selectedIconTheme] are null, then their
  125   /// [NavigationRailThemeData] values will be used. If the corresponding
  126   /// [NavigationRailThemeData] property is null, then the navigation rail
  127   /// defaults are used. See the individual properties for more information.
  128   ///
  129   /// Typically used within a [Row] that defines the [Scaffold.body] property.
  130   const NavigationRail({
  131     this.backgroundColor,
  132     this.extended = false,
  133     this.leading,
  134     this.trailing,
  135     @required this.destinations,
  136     @required this.selectedIndex,
  137     this.onDestinationSelected,
  138     this.elevation,
  139     this.groupAlignment,
  140     this.labelType,
  141     this.unselectedLabelTextStyle,
  142     this.selectedLabelTextStyle,
  143     this.unselectedIconTheme,
  144     this.selectedIconTheme,
  145     this.minWidth,
  146     this.minExtendedWidth,
  147   }) :  assert(destinations != null && destinations.length >= 2),
  148         assert(selectedIndex != null),
  149         assert(0 <= selectedIndex && selectedIndex < destinations.length),
  150         assert(elevation == null || elevation > 0),
  151         assert(minWidth == null || minWidth > 0),
  152         assert(minExtendedWidth == null || minExtendedWidth > 0),
  153         assert((minWidth == null || minExtendedWidth == null) || minExtendedWidth >= minWidth),
  154         assert(extended != null),
  155         assert(!extended || (labelType == null || labelType == NavigationRailLabelType.none));
  156 
  157   /// Sets the color of the Container that holds all of the [NavigationRail]'s
  158   /// contents.
  159   ///
  160   /// The default value is [NavigationRailThemeData.backgroundColor]. If
  161   /// [NavigationRailThemeData.backgroundColor] is null, then the default value
  162   /// is based on [ColorScheme.surface] of [ThemeData.colorScheme].
  163   final Color backgroundColor;
  164 
  165   /// Indicates that the [NavigationRail] should be in the extended state.
  166   ///
  167   /// The extended state has a wider rail container, and the labels are
  168   /// positioned next to the icons. [minExtendedWidth] can be used to set the
  169   /// minimum width of the rail when it is in this state.
  170   ///
  171   /// The rail will implicitly animate between the extended and normal state.
  172   ///
  173   /// If the rail is going to be in the extended state, then the [labelType]
  174   /// must be set to [NavigationRailLabelType.none].
  175   ///
  176   /// The default value is false.
  177   final bool extended;
  178 
  179   /// The leading widget in the rail that is placed above the destinations.
  180   ///
  181   /// It is placed at the top of the rail, above the [destinations]. Its
  182   /// location is not affected by [groupAlignment].
  183   ///
  184   /// This is commonly a [FloatingActionButton], but may also be a non-button,
  185   /// such as a logo.
  186   ///
  187   /// The default value is null.
  188   final Widget leading;
  189 
  190   /// The trailing widget in the rail that is placed below the destinations.
  191   ///
  192   /// The trailing widget is placed below the last [NavigationRailDestination].
  193   /// It's location is affected by [groupAlignment].
  194   ///
  195   /// This is commonly a list of additional options or destinations that is
  196   /// usually only rendered when [extended] is true.
  197   ///
  198   /// The default value is null.
  199   final Widget trailing;
  200 
  201   /// Defines the appearance of the button items that are arrayed within the
  202   /// navigation rail.
  203   ///
  204   /// The value must be a list of two or more [NavigationRailDestination]
  205   /// values.
  206   final List<NavigationRailDestination> destinations;
  207 
  208   /// The index into [destinations] for the current selected
  209   /// [NavigationRailDestination].
  210   final int selectedIndex;
  211 
  212   /// Called when one of the [destinations] is selected.
  213   ///
  214   /// The stateful widget that creates the navigation rail needs to keep
  215   /// track of the index of the selected [NavigationRailDestination] and call
  216   /// `setState` to rebuild the navigation rail with the new [selectedIndex].
  217   final ValueChanged<int> onDestinationSelected;
  218 
  219   /// The rail's elevation or z-coordinate.
  220   ///
  221   /// If [Directionality] is [TextDirection.LTR], the inner side is the right
  222   /// side, and if [Directionality] is [TextDirection.RTL], it is the left side.
  223   ///
  224   /// The default value is 0.
  225   final double elevation;
  226 
  227   /// The vertical alignment for the group of [destinations] within the rail.
  228   ///
  229   /// The [NavigationRailDestination]s are grouped together with the [trailing]
  230   /// widget, between the [leading] widget and the bottom of the rail.
  231   ///
  232   /// The value must be between -1.0 and 1.0.
  233   ///
  234   /// If [groupAlignment] is -1.0, then the items are aligned to the top. If
  235   /// [groupAlignment] is 0.0, then the items are aligned to the center. If
  236   /// [groupAlignment] is 1.0, then the items are aligned to the bottom.
  237   ///
  238   /// The default is -1.0.
  239   ///
  240   /// See also:
  241   ///   * [Alignment.y]
  242   ///
  243   final double groupAlignment;
  244 
  245   /// Defines the layout and behavior of the labels for the default, unextended
  246   /// [NavigationRail].
  247   ///
  248   /// When a navigation rail is [extended], the labels are always shown.
  249   ///
  250   /// The default value is [NavigationRailThemeData.labelType]. If
  251   /// [NavigationRailThemeData.labelType] is null, then the default value is
  252   /// [NavigationRailLabelType.none].
  253   ///
  254   /// See also:
  255   ///
  256   ///   * [NavigationRailLabelType] for information on the meaning of different
  257   ///   types.
  258   final NavigationRailLabelType labelType;
  259 
  260   /// The [TextStyle] of a destination's label when it is unselected.
  261   ///
  262   /// When one of the [destinations] is selected the [selectedLabelTextStyle]
  263   /// will be used instead.
  264   ///
  265   /// The default value is based on the [Theme]'s [TextTheme.bodyText1]. The
  266   /// default color is based on the [Theme]'s [ColorScheme.onSurface].
  267   ///
  268   /// Properties from this text style, or
  269   /// [NavigationRailThemeData.unselectedLabelTextStyle] if this is null, are
  270   /// merged into the defaults.
  271   final TextStyle unselectedLabelTextStyle;
  272 
  273   /// The [TextStyle] of a destination's label when it is selected.
  274   ///
  275   /// When a [NavigationRailDestination] is not selected,
  276   /// [unselectedLabelTextStyle] will be used.
  277   ///
  278   /// The default value is based on the [TextTheme.bodyText1] of
  279   /// [ThemeData.textTheme]. The default color is based on the [Theme]'s
  280   /// [ColorScheme.primary].
  281   ///
  282   /// Properties from this text style,
  283   /// or [NavigationRailThemeData.selectedLabelTextStyle] if this is null, are
  284   /// merged into the defaults.
  285   final TextStyle selectedLabelTextStyle;
  286 
  287   /// The visual properties of the icon in the unselected destination.
  288   ///
  289   /// If this field is not provided, or provided with any null properties, then
  290   /// a copy of the [IconThemeData.fallback] with a custom [NavigationRail]
  291   /// specific color will be used.
  292   ///
  293   /// The default value is Is the [Theme]'s [ThemeData.iconTheme] with a color
  294   /// of the [Theme]'s [ColorScheme.onSurface] with an opacity of 0.64.
  295   /// Properties from this icon theme, or
  296   /// [NavigationRailThemeData.unselectedIconTheme] if this is null, are
  297   /// merged into the defaults.
  298   final IconThemeData unselectedIconTheme;
  299 
  300   /// The visual properties of the icon in the selected destination.
  301   ///
  302   /// When a [NavigationRailDestination] is not selected,
  303   /// [unselectedIconTheme] will be used.
  304   ///
  305   /// The default value is Is the [Theme]'s [ThemeData.iconTheme] with a color
  306   /// of the [Theme]'s [ColorScheme.primary]. Properties from this icon theme,
  307   /// or [NavigationRailThemeData.selectedIconTheme] if this is null, are
  308   /// merged into the defaults.
  309   final IconThemeData selectedIconTheme;
  310 
  311   /// The smallest possible width for the rail regardless of the destination's
  312   /// icon or label size.
  313   ///
  314   /// The default is 72.
  315   ///
  316   /// This value also defines the min width and min height of the destinations.
  317   ///
  318   /// To make a compact rail, set this to 56 and use
  319   /// [NavigationRailLabelType.none].
  320   final double minWidth;
  321 
  322   /// The final width when the animation is complete for setting [extended] to
  323   /// true.
  324   ///
  325   /// This is only used when [extended] is set to true.
  326   ///
  327   /// The default value is 256.
  328   final double minExtendedWidth;
  329 
  330   /// Returns the animation that controls the [NavigationRail.extended] state.
  331   ///
  332   /// This can be used to synchronize animations in the [leading] or [trailing]
  333   /// widget, such as an animated menu or a [FloatingActionButton] animation.
  334   ///
  335   /// {@tool snippet}
  336   ///
  337   /// This example shows how to use this animation to create a
  338   /// [FloatingActionButton] that animates itself between the normal and
  339   /// extended states of the [NavigationRail].
  340   ///
  341   /// An instance of `ExtendableFab` would be created for
  342   /// [NavigationRail.leading].
  343   ///
  344   /// ```dart
  345   /// import 'dart:ui';
  346   ///
  347   /// @override
  348   /// Widget build(BuildContext context) {
  349   ///   final Animation<double> animation = NavigationRail.extendedAnimation(context);
  350   ///   return AnimatedBuilder(
  351   ///     animation: animation,
  352   ///     builder: (BuildContext context, Widget child) {
  353   ///       // The extended fab has a shorter height than the regular fab.
  354   ///       return Container(
  355   ///         height: 56,
  356   ///         padding: EdgeInsets.symmetric(
  357   ///           vertical: lerpDouble(0, 6, animation.value),
  358   ///         ),
  359   ///         child: animation.value == 0
  360   ///           ? FloatingActionButton(
  361   ///               child: Icon(Icons.add),
  362   ///               onPressed: () {},
  363   ///             )
  364   ///           : Align(
  365   ///               alignment: AlignmentDirectional.centerStart,
  366   ///               widthFactor: animation.value,
  367   ///               child: Padding(
  368   ///                 padding: const EdgeInsetsDirectional.only(start: 8),
  369   ///                 child: FloatingActionButton.extended(
  370   ///                   icon: Icon(Icons.add),
  371   ///                   label: Text('CREATE'),
  372   ///                   onPressed: () {},
  373   ///                 ),
  374   ///               ),
  375   ///             ),
  376   ///       );
  377   ///     },
  378   ///   );
  379   /// }
  380   /// ```
  381   ///
  382   /// {@end-tool}
  383   static Animation<double> extendedAnimation(BuildContext context) {
  384     return context.dependOnInheritedWidgetOfExactType<_ExtendedNavigationRailAnimation>().animation;
  385   }
  386 
  387   @override
  388   _NavigationRailState createState() => _NavigationRailState();
  389 }
  390 
  391 class _NavigationRailState extends State<NavigationRail> with TickerProviderStateMixin {
  392   List<AnimationController> _destinationControllers = <AnimationController>[];
  393   List<Animation<double>> _destinationAnimations;
  394   AnimationController _extendedController;
  395   Animation<double> _extendedAnimation;
  396 
  397   @override
  398   void initState() {
  399     super.initState();
  400     _initControllers();
  401   }
  402 
  403   @override
  404   void dispose() {
  405     _disposeControllers();
  406     super.dispose();
  407   }
  408 
  409   @override
  410   void didUpdateWidget(NavigationRail oldWidget) {
  411     super.didUpdateWidget(oldWidget);
  412 
  413     if (widget.extended != oldWidget.extended) {
  414       if (widget.extended) {
  415         _extendedController.forward();
  416       } else {
  417         _extendedController.reverse();
  418       }
  419     }
  420 
  421     // No animated segue if the length of the items list changes.
  422     if (widget.destinations.length != oldWidget.destinations.length) {
  423       _resetState();
  424       return;
  425     }
  426 
  427     if (widget.selectedIndex != oldWidget.selectedIndex) {
  428       _destinationControllers[oldWidget.selectedIndex].reverse();
  429       _destinationControllers[widget.selectedIndex].forward();
  430       return;
  431     }
  432   }
  433 
  434   @override
  435   Widget build(BuildContext context) {
  436     final ThemeData theme = Theme.of(context);
  437     final NavigationRailThemeData navigationRailTheme = NavigationRailTheme.of(context);
  438     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
  439 
  440     final Color backgroundColor = widget.backgroundColor ?? navigationRailTheme.backgroundColor ?? theme.colorScheme.surface;
  441     final double elevation = widget.elevation ?? navigationRailTheme.elevation ?? 0;
  442     final double minWidth = widget.minWidth ?? _minRailWidth;
  443     final double minExtendedWidth = widget.minExtendedWidth ?? _minExtendedRailWidth;
  444     final Color baseSelectedColor = theme.colorScheme.primary;
  445     final Color baseUnselectedColor = theme.colorScheme.onSurface.withOpacity(0.64);
  446     final IconThemeData defaultUnselectedIconTheme = widget.unselectedIconTheme ?? navigationRailTheme.unselectedIconTheme;
  447     final IconThemeData unselectedIconTheme = IconThemeData(
  448       size: defaultUnselectedIconTheme?.size ?? 24.0,
  449       color: defaultUnselectedIconTheme?.color ?? theme.colorScheme.onSurface,
  450       opacity: defaultUnselectedIconTheme?.opacity ?? 0.64,
  451     );
  452     final IconThemeData defaultSelectedIconTheme = widget.selectedIconTheme ?? navigationRailTheme.selectedIconTheme;
  453     final IconThemeData selectedIconTheme = IconThemeData(
  454       size: defaultSelectedIconTheme?.size ?? 24.0,
  455       color: defaultSelectedIconTheme?.color ?? theme.colorScheme.primary,
  456       opacity: defaultSelectedIconTheme?.opacity ?? 1.0,
  457     );
  458     final TextStyle unselectedLabelTextStyle = theme.textTheme.bodyText1.copyWith(color: baseUnselectedColor).merge(widget.unselectedLabelTextStyle ?? navigationRailTheme.unselectedLabelTextStyle);
  459     final TextStyle selectedLabelTextStyle = theme.textTheme.bodyText1.copyWith(color: baseSelectedColor).merge(widget.selectedLabelTextStyle ?? navigationRailTheme.selectedLabelTextStyle);
  460     final double groupAlignment = widget.groupAlignment ?? navigationRailTheme.groupAlignment ?? -1.0;
  461     final NavigationRailLabelType labelType = widget.labelType ?? navigationRailTheme.labelType ?? NavigationRailLabelType.none;
  462 
  463     return _ExtendedNavigationRailAnimation(
  464       animation: _extendedAnimation,
  465       child: Semantics(
  466         explicitChildNodes: true,
  467         child: Material(
  468           elevation: elevation,
  469           color: backgroundColor,
  470           child: Column(
  471             children: <Widget>[
  472               _verticalSpacer,
  473               if (widget.leading != null)
  474                 ...<Widget>[
  475                   ConstrainedBox(
  476                     constraints: BoxConstraints(
  477                       minWidth: lerpDouble(minWidth, minExtendedWidth, _extendedAnimation.value),
  478                     ),
  479                     child: widget.leading,
  480                   ),
  481                   _verticalSpacer,
  482                 ],
  483               Expanded(
  484                 child: Align(
  485                   alignment: Alignment(0, groupAlignment),
  486                   child: Column(
  487                     mainAxisSize: MainAxisSize.min,
  488                     children: <Widget>[
  489                       for (int i = 0; i < widget.destinations.length; i += 1)
  490                         _RailDestination(
  491                           minWidth: minWidth,
  492                           minExtendedWidth: minExtendedWidth,
  493                           extendedTransitionAnimation: _extendedAnimation,
  494                           selected: widget.selectedIndex == i,
  495                           icon: widget.selectedIndex == i ? widget.destinations[i].selectedIcon : widget.destinations[i].icon,
  496                           label: widget.destinations[i].label,
  497                           destinationAnimation: _destinationAnimations[i],
  498                           labelType: labelType,
  499                           iconTheme: widget.selectedIndex == i ? selectedIconTheme : unselectedIconTheme,
  500                           labelTextStyle: widget.selectedIndex == i ? selectedLabelTextStyle : unselectedLabelTextStyle,
  501                           onTap: () {
  502                             widget.onDestinationSelected(i);
  503                           },
  504                           indexLabel: localizations.tabLabel(
  505                             tabIndex: i + 1,
  506                             tabCount: widget.destinations.length,
  507                           ),
  508                         ),
  509                       if (widget.trailing != null)
  510                         ConstrainedBox(
  511                           constraints: BoxConstraints(
  512                             minWidth: lerpDouble(minWidth, minExtendedWidth, _extendedAnimation.value),
  513                           ),
  514                           child: widget.trailing,
  515                         ),
  516                     ],
  517                   ),
  518                 ),
  519               ),
  520             ],
  521           ),
  522         ),
  523       ),
  524     );
  525   }
  526 
  527   void _disposeControllers() {
  528     for (final AnimationController controller in _destinationControllers) {
  529       controller.dispose();
  530     }
  531     _extendedController.dispose();
  532   }
  533 
  534   void _initControllers() {
  535     _destinationControllers = List<AnimationController>.generate(widget.destinations.length, (int index) {
  536       return AnimationController(
  537         duration: kThemeAnimationDuration,
  538         vsync: this,
  539       )..addListener(_rebuild);
  540     });
  541     _destinationAnimations = _destinationControllers.map((AnimationController controller) => controller.view).toList();
  542     _destinationControllers[widget.selectedIndex].value = 1.0;
  543     _extendedController = AnimationController(
  544       duration: kThemeAnimationDuration,
  545       vsync: this,
  546       value: widget.extended ? 1.0 : 0.0,
  547     );
  548     _extendedAnimation = CurvedAnimation(
  549       parent: _extendedController,
  550       curve: Curves.easeInOut,
  551     );
  552     _extendedController.addListener(() {
  553       _rebuild();
  554     });
  555   }
  556 
  557   void _resetState() {
  558     _disposeControllers();
  559     _initControllers();
  560   }
  561 
  562   void _rebuild() {
  563     setState(() {
  564       // Rebuilding when any of the controllers tick, i.e. when the items are
  565       // animating.
  566     });
  567   }
  568 }
  569 
  570 class _RailDestination extends StatelessWidget {
  571   _RailDestination({
  572     @required this.minWidth,
  573     @required this.minExtendedWidth,
  574     @required this.icon,
  575     @required this.label,
  576     @required this.destinationAnimation,
  577     @required this.extendedTransitionAnimation,
  578     @required this.labelType,
  579     @required this.selected,
  580     @required this.iconTheme,
  581     @required this.labelTextStyle,
  582     @required this.onTap,
  583     @required this.indexLabel,
  584   }) : assert(minWidth != null),
  585        assert(minExtendedWidth != null),
  586        assert(icon != null),
  587        assert(label != null),
  588        assert(destinationAnimation != null),
  589        assert(extendedTransitionAnimation != null),
  590        assert(labelType != null),
  591        assert(selected != null),
  592        assert(iconTheme != null),
  593        assert(labelTextStyle != null),
  594        assert(onTap != null),
  595        assert(indexLabel != null),
  596        _positionAnimation = CurvedAnimation(
  597           parent: ReverseAnimation(destinationAnimation),
  598           curve: Curves.easeInOut,
  599           reverseCurve: Curves.easeInOut.flipped,
  600        );
  601 
  602   final double minWidth;
  603   final double minExtendedWidth;
  604   final Widget icon;
  605   final Widget label;
  606   final Animation<double> destinationAnimation;
  607   final NavigationRailLabelType labelType;
  608   final bool selected;
  609   final Animation<double> extendedTransitionAnimation;
  610   final IconThemeData iconTheme;
  611   final TextStyle labelTextStyle;
  612   final VoidCallback onTap;
  613   final String indexLabel;
  614 
  615   final Animation<double> _positionAnimation;
  616 
  617   @override
  618   Widget build(BuildContext context) {
  619     final Widget themedIcon = IconTheme(
  620       data: iconTheme,
  621       child: icon,
  622     );
  623     final Widget styledLabel = DefaultTextStyle(
  624       style: labelTextStyle,
  625       child: label,
  626     );
  627     Widget content;
  628     switch (labelType) {
  629       case NavigationRailLabelType.none:
  630         final Widget iconPart = SizedBox(
  631           width: minWidth,
  632           height: minWidth,
  633           child: Align(
  634             alignment: Alignment.center,
  635             child: themedIcon,
  636           ),
  637         );
  638         if (extendedTransitionAnimation.value == 0) {
  639           content = Stack(
  640             children: <Widget>[
  641               iconPart,
  642               // For semantics when label is not showing,
  643               SizedBox(
  644                 width: 0,
  645                 height: 0,
  646                 child: Opacity(
  647                   alwaysIncludeSemantics: true,
  648                   opacity: 0.0,
  649                   child: label,
  650                 ),
  651               ),
  652             ]
  653           );
  654         } else {
  655           content = ConstrainedBox(
  656             constraints: BoxConstraints(
  657               minWidth: lerpDouble(minWidth, minExtendedWidth, extendedTransitionAnimation.value),
  658             ),
  659             child: ClipRect(
  660               child: Row(
  661                 children: <Widget>[
  662                   iconPart,
  663                   Align(
  664                     heightFactor: 1.0,
  665                     widthFactor: extendedTransitionAnimation.value,
  666                     alignment: AlignmentDirectional.centerStart,
  667                     child: Opacity(
  668                       alwaysIncludeSemantics: true,
  669                       opacity: _extendedLabelFadeValue(),
  670                       child: styledLabel,
  671                     ),
  672                   ),
  673                   const SizedBox(width: _horizontalDestinationPadding),
  674                 ],
  675               ),
  676             ),
  677           );
  678         }
  679         break;
  680       case NavigationRailLabelType.selected:
  681         final double appearingAnimationValue = 1 - _positionAnimation.value;
  682         final double verticalPadding = lerpDouble(_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue);
  683         content = Container(
  684           constraints: BoxConstraints(
  685             minWidth: minWidth,
  686             minHeight: minWidth,
  687           ),
  688           padding: const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
  689           child: ClipRect(
  690             child: Column(
  691               mainAxisSize: MainAxisSize.min,
  692               mainAxisAlignment: MainAxisAlignment.center,
  693               children: <Widget>[
  694                 SizedBox(height: verticalPadding),
  695                 themedIcon,
  696                 Align(
  697                   alignment: Alignment.topCenter,
  698                   heightFactor: appearingAnimationValue,
  699                   widthFactor: 1.0,
  700                   child: Opacity(
  701                     alwaysIncludeSemantics: true,
  702                     opacity: selected ? _normalLabelFadeInValue() : _normalLabelFadeOutValue(),
  703                     child: styledLabel,
  704                   ),
  705                 ),
  706                 SizedBox(height: verticalPadding),
  707               ],
  708             ),
  709           ),
  710         );
  711         break;
  712       case NavigationRailLabelType.all:
  713         content = Container(
  714           constraints: BoxConstraints(
  715             minWidth: minWidth,
  716             minHeight: minWidth,
  717           ),
  718           padding: const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
  719           child: Column(
  720             children: <Widget>[
  721               const SizedBox(height: _verticalDestinationPaddingWithLabel),
  722               themedIcon,
  723               styledLabel,
  724               const SizedBox(height: _verticalDestinationPaddingWithLabel),
  725             ],
  726           ),
  727         );
  728         break;
  729     }
  730 
  731     final ColorScheme colors = Theme.of(context).colorScheme;
  732     return Semantics(
  733       container: true,
  734       selected: selected,
  735       child: Stack(
  736         children: <Widget>[
  737           Material(
  738             type: MaterialType.transparency,
  739             clipBehavior: Clip.none,
  740             child: InkResponse(
  741               onTap: onTap,
  742               onHover: (_) {},
  743               highlightShape: BoxShape.rectangle,
  744               borderRadius: BorderRadius.all(Radius.circular(minWidth / 2.0)),
  745               containedInkWell: true,
  746               splashColor: colors.primary.withOpacity(0.12),
  747               hoverColor: colors.primary.withOpacity(0.04),
  748               child: content,
  749             ),
  750           ),
  751           Semantics(
  752             label: indexLabel,
  753           ),
  754         ]
  755       ),
  756     );
  757   }
  758 
  759   double _normalLabelFadeInValue() {
  760     if (destinationAnimation.value < 0.25) {
  761       return 0;
  762     } else if (destinationAnimation.value < 0.75) {
  763       return (destinationAnimation.value - 0.25) * 2;
  764     } else {
  765       return 1;
  766     }
  767   }
  768 
  769   double _normalLabelFadeOutValue() {
  770     if (destinationAnimation.value > 0.75) {
  771       return (destinationAnimation.value - 0.75) * 4.0;
  772     } else {
  773       return 0;
  774     }
  775   }
  776 
  777   double _extendedLabelFadeValue() {
  778     return extendedTransitionAnimation.value < 0.25 ? extendedTransitionAnimation.value * 4.0 : 1.0;
  779   }
  780 }
  781 
  782 /// Defines the behavior of the labels of a [NavigationRail].
  783 ///
  784 /// See also:
  785 ///
  786 ///   * [NavigationRail]
  787 enum NavigationRailLabelType {
  788   /// Only the [NavigationRailDestination]s are shown.
  789   none,
  790 
  791   /// Only the selected [NavigationRailDestination] will show its label.
  792   ///
  793   /// The label will animate in and out as new [NavigationRailDestination]s are
  794   /// selected.
  795   selected,
  796 
  797   /// All [NavigationRailDestination]s will show their label.
  798   all,
  799 }
  800 
  801 /// Defines a [NavigationRail] button that represents one "destination" view.
  802 ///
  803 /// See also:
  804 ///
  805 ///  * [NavigationRail]
  806 class NavigationRailDestination {
  807   /// Creates a destination that is used with [NavigationRail.destinations].
  808   ///
  809   /// [icon] and [label] must be non-null. When the [NavigationRail.labelType]
  810   /// is [NavigationRailLabelType.none], the label is still used for semantics,
  811   /// and may still be used if [NavigationRail.extended] is true.
  812   const NavigationRailDestination({
  813     @required this.icon,
  814     Widget selectedIcon,
  815     this.label,
  816   }) : selectedIcon = selectedIcon ?? icon,
  817        assert(icon != null);
  818 
  819   /// The icon of the destination.
  820   ///
  821   /// Typically the icon is an [Icon] or an [ImageIcon] widget. If another type
  822   /// of widget is provided then it should configure itself to match the current
  823   /// [IconTheme] size and color.
  824   ///
  825   /// If [selectedIcon] is provided, this will only be displayed when the
  826   /// destination is not selected.
  827   ///
  828   /// To make the [NavigationRail] more accessible, consider choosing an
  829   /// icon with a stroked and filled version, such as [Icons.cloud] and
  830   /// [Icons.cloud_queue]. The [icon] should be set to the stroked version and
  831   /// [selectedIcon] to the filled version.
  832   final Widget icon;
  833 
  834   /// An alternative icon displayed when this destination is selected.
  835   ///
  836   /// If this icon is not provided, the [NavigationRail] will display [icon] in
  837   /// either state. The size, color, and opacity of the
  838   /// [NavigationRail.selectedIconTheme] will still apply.
  839   ///
  840   /// See also:
  841   ///
  842   ///  * [NavigationRailDestination.icon], for a description of how to pair
  843   ///    icons.
  844   final Widget selectedIcon;
  845 
  846   /// The label for the destination.
  847   ///
  848   /// The label must be provided when used with the [NavigationRail]. When the
  849   /// [NavigationRail.labelType] is [NavigationRailLabelType.none], the label is
  850   /// still used for semantics, and may still be used if
  851   /// [NavigationRail.extended] is true.
  852   final Widget label;
  853 }
  854 
  855 class _ExtendedNavigationRailAnimation extends InheritedWidget {
  856   const _ExtendedNavigationRailAnimation({
  857     Key key,
  858     @required this.animation,
  859     @required Widget child,
  860   }) : assert(child != null),
  861        super(key: key, child: child);
  862 
  863   final Animation<double> animation;
  864 
  865   @override
  866   bool updateShouldNotify(_ExtendedNavigationRailAnimation old) => animation != old.animation;
  867 }
  868 
  869 const double _minRailWidth = 72.0;
  870 const double _minExtendedRailWidth = 256.0;
  871 const double _horizontalDestinationPadding = 8.0;
  872 const double _verticalDestinationPaddingNoLabel = 24.0;
  873 const double _verticalDestinationPaddingWithLabel = 16.0;
  874 const Widget _verticalSpacer = SizedBox(height: 8.0);