| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/widgets.dart'; |
| |
| import 'color_scheme.dart'; |
| import 'colors.dart'; |
| import 'drawer.dart'; |
| import 'ink_well.dart'; |
| import 'material.dart'; |
| import 'material_localizations.dart'; |
| import 'material_state.dart'; |
| import 'navigation_bar.dart'; |
| import 'navigation_drawer_theme.dart'; |
| import 'text_theme.dart'; |
| import 'theme.dart'; |
| |
| /// Material Design Navigation Drawer component. |
| /// |
| /// On top of [Drawer]s, Navigation drawers offer a persistent and convenient way to switch |
| /// between primary destinations in an app. |
| /// |
| /// The style for the icons and text are not affected by parent |
| /// [DefaultTextStyle]s or [IconTheme]s but rather controlled by parameters or |
| /// the [NavigationDrawerThemeData]. |
| /// |
| /// The [children] are a list of widgets to be displayed in the drawer. These can be a |
| /// mixture of any widgets, but there is special handling for [NavigationDrawerDestination]s. |
| /// They are treated as a group and when one is selected, the [onDestinationSelected] |
| /// is called with the index into the group that corresponds to the selected destination. |
| /// |
| /// {@tool dartpad} |
| /// This example shows a [NavigationDrawer] used within a [Scaffold] |
| /// widget. The [NavigationDrawer] has headline widget, divider widget and three |
| /// [NavigationDrawerDestination] widgets. The initial [selectedIndex] is 0. |
| /// The [onDestinationSelected] callback changes the selected item's index and displays |
| /// a corresponding widget in the body of the [Scaffold]. |
| /// |
| /// ** See code in examples/api/lib/material/navigation_drawer/navigation_drawer.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [Scaffold.drawer], where one specifies a [Drawer] so that it can be |
| /// shown. |
| /// * [Scaffold.of], to obtain the current [ScaffoldState], which manages the |
| /// display and animation of the drawer. |
| /// * [ScaffoldState.openDrawer], which displays its [Drawer], if any. |
| /// * <https://material.io/design/components/navigation-drawer.html> |
| class NavigationDrawer extends StatelessWidget { |
| /// Creates a Material Design Navigation Drawer component. |
| const NavigationDrawer({ |
| super.key, |
| required this.children, |
| this.backgroundColor, |
| this.shadowColor, |
| this.surfaceTintColor, |
| this.elevation, |
| this.indicatorColor, |
| this.indicatorShape, |
| this.onDestinationSelected, |
| this.selectedIndex = 0, |
| this.tilePadding = const EdgeInsets.symmetric(horizontal: 12.0), |
| }); |
| |
| /// The background color of the [Material] that holds the [NavigationDrawer]'s |
| /// contents. |
| /// |
| /// If this is null, then [NavigationDrawerThemeData.backgroundColor] is used. |
| /// If that is also null, then it falls back to [ColorScheme.surface]. |
| final Color? backgroundColor; |
| |
| /// The color used for the drop shadow to indicate elevation. |
| /// |
| /// If null, [NavigationDrawerThemeData.shadowColor] is used. If that |
| /// is also null, the default value is [Colors.transparent] which |
| /// indicates that no drop shadow will be displayed. |
| /// |
| /// See [Material.shadowColor] for more details on drop shadows. |
| final Color? shadowColor; |
| |
| /// The surface tint of the [Material] that holds the [NavigationDrawer]'s |
| /// contents. |
| /// |
| /// If this is null, then [NavigationDrawerThemeData.surfaceTintColor] is used. |
| /// If that is also null, then it falls back to [Material.surfaceTintColor]'s default. |
| final Color? surfaceTintColor; |
| |
| /// The elevation of the [NavigationDrawer] itself. |
| /// |
| /// If null, [NavigationDrawerThemeData.elevation] is used. If that |
| /// is also null, it will be 1.0. |
| final double? elevation; |
| |
| /// The color of the [indicatorShape] when this destination is selected. |
| /// |
| /// If this is null, [NavigationDrawerThemeData.indicatorColor] is used. |
| /// If that is also null, defaults to [ColorScheme.secondaryContainer]. |
| final Color? indicatorColor; |
| |
| /// The shape of the selected indicator. |
| /// |
| /// If this is null, [NavigationDrawerThemeData.indicatorShape] is used. |
| /// If that is also null, defaults to [StadiumBorder]. |
| final ShapeBorder? indicatorShape; |
| |
| /// Defines the appearance of the items within the navigation drawer. |
| /// |
| /// The list contains [NavigationDrawerDestination] widgets and/or customized |
| /// widgets like headlines and dividers. |
| final List<Widget> children; |
| |
| /// The index into destinations for the current selected |
| /// [NavigationDrawerDestination] or null if no destination is selected. |
| /// |
| /// A valid [selectedIndex] satisfies 0 <= [selectedIndex] < number of [NavigationDrawerDestination]. |
| /// For an invalid [selectedIndex] like `-1`, all destinations will appear unselected. |
| final int? selectedIndex; |
| |
| /// Called when one of the [NavigationDrawerDestination] children is selected. |
| /// |
| /// This callback usually updates the int passed to [selectedIndex]. |
| /// |
| /// Upon updating [selectedIndex], the [NavigationDrawer] will be rebuilt. |
| final ValueChanged<int>? onDestinationSelected; |
| |
| /// Defines the padding for [NavigationDrawerDestination] widgets (Drawer items). |
| /// |
| /// Defaults to `EdgeInsets.symmetric(horizontal: 12.0)`. |
| final EdgeInsetsGeometry tilePadding; |
| |
| @override |
| Widget build(BuildContext context) { |
| final int totalNumberOfDestinations = |
| children.whereType<NavigationDrawerDestination>().toList().length; |
| |
| int destinationIndex = 0; |
| final List<Widget> wrappedChildren = <Widget>[]; |
| Widget wrapChild(Widget child, int index) => _SelectableAnimatedBuilder( |
| duration: const Duration(milliseconds: 500), |
| isSelected: index == selectedIndex, |
| builder: (BuildContext context, Animation<double> animation) { |
| return _NavigationDrawerDestinationInfo( |
| index: index, |
| totalNumberOfDestinations: totalNumberOfDestinations, |
| selectedAnimation: animation, |
| indicatorColor: indicatorColor, |
| indicatorShape: indicatorShape, |
| tilePadding: tilePadding, |
| onTap: () { |
| if (onDestinationSelected != null) { |
| onDestinationSelected!(index); |
| } |
| }, |
| child: child, |
| ); |
| }); |
| |
| for (int i = 0; i < children.length; i++) { |
| if (children[i] is! NavigationDrawerDestination) { |
| wrappedChildren.add(children[i]); |
| } else { |
| wrappedChildren.add(wrapChild(children[i], destinationIndex)); |
| destinationIndex += 1; |
| } |
| } |
| final NavigationDrawerThemeData navigationDrawerTheme = NavigationDrawerTheme.of(context); |
| |
| return Drawer( |
| backgroundColor: backgroundColor ?? navigationDrawerTheme.backgroundColor, |
| shadowColor: shadowColor ?? navigationDrawerTheme.shadowColor, |
| surfaceTintColor: surfaceTintColor ?? navigationDrawerTheme.surfaceTintColor, |
| elevation: elevation ?? navigationDrawerTheme.elevation, |
| child: SafeArea( |
| bottom: false, |
| child: ListView( |
| children: wrappedChildren, |
| ), |
| ), |
| ); |
| } |
| } |
| |
| /// A Material Design [NavigationDrawer] destination. |
| /// |
| /// Displays an icon with a label, for use in [NavigationDrawer.children]. |
| class NavigationDrawerDestination extends StatelessWidget { |
| /// Creates a navigation drawer destination. |
| const NavigationDrawerDestination({ |
| super.key, |
| this.backgroundColor, |
| required this.icon, |
| this.selectedIcon, |
| required this.label, |
| }); |
| |
| /// Sets the color of the [Material] that holds all of the [Drawer]'s |
| /// contents. |
| /// |
| /// If this is null, then [DrawerThemeData.backgroundColor] is used. If that |
| /// is also null, then it falls back to [Material]'s default. |
| final Color? backgroundColor; |
| |
| /// The [Widget] (usually an [Icon]) that's displayed for this |
| /// [NavigationDestination]. |
| /// |
| /// The icon will use [NavigationDrawerThemeData.iconTheme]. If this is |
| /// null, the default [IconThemeData] would use a size of 24.0 and |
| /// [ColorScheme.onSurfaceVariant]. |
| final Widget icon; |
| |
| /// The optional [Widget] (usually an [Icon]) that's displayed when this |
| /// [NavigationDestination] is selected. |
| /// |
| /// If [selectedIcon] is non-null, the destination will fade from |
| /// [icon] to [selectedIcon] when this destination goes from unselected to |
| /// selected. |
| /// |
| /// The icon will use [NavigationDrawerThemeData.iconTheme] with |
| /// [MaterialState.selected]. If this is null, the default [IconThemeData] |
| /// would use a size of 24.0 and [ColorScheme.onSecondaryContainer]. |
| final Widget? selectedIcon; |
| |
| /// The text label that appears on the right of the icon |
| /// |
| /// The accompanying [Text] widget will use |
| /// [NavigationDrawerThemeData.labelTextStyle]. If this are null, the default |
| /// text style would use [TextTheme.labelLarge] with [ColorScheme.onSurfaceVariant]. |
| final Widget label; |
| |
| @override |
| Widget build(BuildContext context) { |
| const Set<MaterialState> selectedState = <MaterialState>{ |
| MaterialState.selected |
| }; |
| const Set<MaterialState> unselectedState = <MaterialState>{}; |
| |
| final NavigationDrawerThemeData navigationDrawerTheme = |
| NavigationDrawerTheme.of(context); |
| final NavigationDrawerThemeData defaults = |
| _NavigationDrawerDefaultsM3(context); |
| |
| final Animation<double> animation = |
| _NavigationDrawerDestinationInfo.of(context).selectedAnimation; |
| |
| return _NavigationDestinationBuilder( |
| buildIcon: (BuildContext context) { |
| final Widget selectedIconWidget = IconTheme.merge( |
| data: navigationDrawerTheme.iconTheme?.resolve(selectedState) ?? |
| defaults.iconTheme!.resolve(selectedState)!, |
| child: selectedIcon ?? icon, |
| ); |
| final Widget unselectedIconWidget = IconTheme.merge( |
| data: navigationDrawerTheme.iconTheme?.resolve(unselectedState) ?? |
| defaults.iconTheme!.resolve(unselectedState)!, |
| child: icon, |
| ); |
| |
| return _isForwardOrCompleted(animation) |
| ? selectedIconWidget |
| : unselectedIconWidget; |
| }, |
| buildLabel: (BuildContext context) { |
| final TextStyle? effectiveSelectedLabelTextStyle = |
| navigationDrawerTheme.labelTextStyle?.resolve(selectedState) ?? |
| defaults.labelTextStyle!.resolve(selectedState); |
| final TextStyle? effectiveUnselectedLabelTextStyle = |
| navigationDrawerTheme.labelTextStyle?.resolve(unselectedState) ?? |
| defaults.labelTextStyle!.resolve(unselectedState); |
| return DefaultTextStyle( |
| style: _isForwardOrCompleted(animation) |
| ? effectiveSelectedLabelTextStyle! |
| : effectiveUnselectedLabelTextStyle!, |
| child: label, |
| ); |
| }, |
| ); |
| } |
| } |
| |
| /// Widget that handles the semantics and layout of a navigation drawer |
| /// destination. |
| /// |
| /// Prefer [NavigationDestination] over this widget, as it is a simpler |
| /// (although less customizable) way to get navigation drawer destinations. |
| /// |
| /// The icon and label of this destination are built with [buildIcon] and |
| /// [buildLabel]. They should build the unselected and selected icon and label |
| /// according to [_NavigationDrawerDestinationInfo.selectedAnimation], where an |
| /// animation value of 0 is unselected and 1 is selected. |
| /// |
| /// See [NavigationDestination] for an example. |
| class _NavigationDestinationBuilder extends StatelessWidget { |
| /// Builds a destination (icon + label) to use in a Material 3 [NavigationDrawer]. |
| const _NavigationDestinationBuilder({ |
| required this.buildIcon, |
| required this.buildLabel, |
| }); |
| |
| /// Builds the icon for a destination in a [NavigationDrawer]. |
| /// |
| /// To animate between unselected and selected, build the icon based on |
| /// [_NavigationDrawerDestinationInfo.selectedAnimation]. When the animation is 0, |
| /// the destination is unselected, when the animation is 1, the destination is |
| /// selected. |
| /// |
| /// The destination is considered selected as soon as the animation is |
| /// increasing or completed, and it is considered unselected as soon as the |
| /// animation is decreasing or dismissed. |
| final WidgetBuilder buildIcon; |
| |
| /// Builds the label for a destination in a [NavigationDrawer]. |
| /// |
| /// To animate between unselected and selected, build the icon based on |
| /// [_NavigationDrawerDestinationInfo.selectedAnimation]. When the animation is |
| /// 0, the destination is unselected, when the animation is 1, the destination |
| /// is selected. |
| /// |
| /// The destination is considered selected as soon as the animation is |
| /// increasing or completed, and it is considered unselected as soon as the |
| /// animation is decreasing or dismissed. |
| final WidgetBuilder buildLabel; |
| |
| @override |
| Widget build(BuildContext context) { |
| final _NavigationDrawerDestinationInfo info = _NavigationDrawerDestinationInfo.of(context); |
| final NavigationDrawerThemeData navigationDrawerTheme = NavigationDrawerTheme.of(context); |
| final NavigationDrawerThemeData defaults = _NavigationDrawerDefaultsM3(context); |
| |
| return Padding( |
| padding: info.tilePadding, |
| child: _NavigationDestinationSemantics( |
| child: SizedBox( |
| height: navigationDrawerTheme.tileHeight ?? defaults.tileHeight, |
| child: InkWell( |
| highlightColor: Colors.transparent, |
| onTap: info.onTap, |
| customBorder: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!, |
| child: Stack( |
| alignment: Alignment.center, |
| children: <Widget>[ |
| NavigationIndicator( |
| animation: info.selectedAnimation, |
| color: info.indicatorColor ?? navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!, |
| shape: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!, |
| width: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).width, |
| height: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).height, |
| ), |
| Row( |
| children: <Widget>[ |
| const SizedBox(width: 16), |
| buildIcon(context), |
| const SizedBox(width: 12), |
| buildLabel(context), |
| ], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| /// Semantics widget for a navigation drawer destination. |
| /// |
| /// Requires a [_NavigationDrawerDestinationInfo] parent (normally provided by the |
| /// [NavigationDrawer] by default). |
| /// |
| /// Provides localized semantic labels to the destination, for example, it will |
| /// read "Home, Tab 1 of 3". |
| /// |
| /// Used by [_NavigationDestinationBuilder]. |
| class _NavigationDestinationSemantics extends StatelessWidget { |
| /// Adds the appropriate semantics for navigation drawer destinations to the |
| /// [child]. |
| const _NavigationDestinationSemantics({ |
| required this.child, |
| }); |
| |
| /// The widget that should receive the destination semantics. |
| final Widget child; |
| |
| @override |
| Widget build(BuildContext context) { |
| final MaterialLocalizations localizations = MaterialLocalizations.of(context); |
| final _NavigationDrawerDestinationInfo destinationInfo = _NavigationDrawerDestinationInfo.of(context); |
| // The AnimationStatusBuilder will make sure that the semantics update to |
| // "selected" when the animation status changes. |
| return _StatusTransitionWidgetBuilder( |
| animation: destinationInfo.selectedAnimation, |
| builder: (BuildContext context, Widget? child) { |
| return Semantics( |
| selected: _isForwardOrCompleted(destinationInfo.selectedAnimation), |
| container: true, |
| child: child, |
| ); |
| }, |
| child: Stack( |
| alignment: Alignment.center, |
| children: <Widget>[ |
| child, |
| Semantics( |
| label: localizations.tabLabel( |
| tabIndex: destinationInfo.index + 1, |
| tabCount: destinationInfo.totalNumberOfDestinations, |
| ), |
| ), |
| ], |
| ), |
| ); |
| } |
| } |
| |
| /// Widget that listens to an animation, and rebuilds when the animation changes |
| /// [AnimationStatus]. |
| /// |
| /// This can be more efficient than just using an [AnimatedBuilder] when you |
| /// only need to rebuild when the [Animation.status] changes, since |
| /// [AnimatedBuilder] rebuilds every time the animation ticks. |
| class _StatusTransitionWidgetBuilder extends StatusTransitionWidget { |
| /// Creates a widget that rebuilds when the given animation changes status. |
| const _StatusTransitionWidgetBuilder({ |
| required super.animation, |
| required this.builder, |
| this.child, |
| }); |
| |
| /// Called every time the [animation] changes [AnimationStatus]. |
| final TransitionBuilder builder; |
| |
| /// The child widget to pass to the [builder]. |
| /// |
| /// If a [builder] callback's return value contains a subtree that does not |
| /// depend on the animation, it's more efficient to build that subtree once |
| /// instead of rebuilding it on every animation status change. |
| /// |
| /// Using this pre-built child is entirely optional, but can improve |
| /// performance in some cases and is therefore a good practice. |
| /// |
| /// See: [AnimatedBuilder.child] |
| final Widget? child; |
| |
| @override |
| Widget build(BuildContext context) => builder(context, child); |
| } |
| |
| /// Inherited widget for passing data from the [NavigationDrawer] to the |
| /// [NavigationDrawer.destinations] children widgets. |
| /// |
| /// Useful for building navigation destinations using: |
| /// `_NavigationDrawerDestinationInfo.of(context)`. |
| class _NavigationDrawerDestinationInfo extends InheritedWidget { |
| /// Adds the information needed to build a navigation destination to the |
| /// [child] and descendants. |
| const _NavigationDrawerDestinationInfo({ |
| required this.index, |
| required this.totalNumberOfDestinations, |
| required this.selectedAnimation, |
| required this.indicatorColor, |
| required this.indicatorShape, |
| required this.onTap, |
| required super.child, |
| required this.tilePadding, |
| }); |
| |
| /// Which destination index is this in the navigation drawer. |
| /// |
| /// For example: |
| /// |
| /// ```dart |
| /// const NavigationDrawer( |
| /// children: <Widget>[ |
| /// Text('Headline'), // This doesn't have index. |
| /// NavigationDrawerDestination( |
| /// // This is destination index 0. |
| /// icon: Icon(Icons.surfing), |
| /// label: Text('Surfing'), |
| /// ), |
| /// NavigationDrawerDestination( |
| /// // This is destination index 1. |
| /// icon: Icon(Icons.support), |
| /// label: Text('Support'), |
| /// ), |
| /// NavigationDrawerDestination( |
| /// // This is destination index 2. |
| /// icon: Icon(Icons.local_hospital), |
| /// label: Text('Hospital'), |
| /// ), |
| /// ] |
| /// ) |
| /// ``` |
| /// |
| /// This is required for semantics, so that each destination can have a label |
| /// "Tab 1 of 3", for example. |
| final int index; |
| |
| /// How many total destinations are in this navigation drawer. |
| /// |
| /// This is required for semantics, so that each destination can have a label |
| /// "Tab 1 of 4", for example. |
| final int totalNumberOfDestinations; |
| |
| /// Indicates whether or not this destination is selected, from 0 (unselected) |
| /// to 1 (selected). |
| final Animation<double> selectedAnimation; |
| |
| /// The color of the indicator. |
| /// |
| /// This is used by destinations to override the indicator color. |
| final Color? indicatorColor; |
| |
| /// The shape of the indicator. |
| /// |
| /// This is used by destinations to override the indicator shape. |
| final ShapeBorder? indicatorShape; |
| |
| /// The callback that should be called when this destination is tapped. |
| /// |
| /// This is computed by calling [NavigationDrawer.onDestinationSelected] |
| /// with [index] passed in. |
| final VoidCallback onTap; |
| |
| /// Defines the padding for [NavigationDrawerDestination] widgets (Drawer items). |
| /// |
| /// Defaults to `EdgeInsets.symmetric(horizontal: 12.0)`. |
| final EdgeInsetsGeometry tilePadding; |
| |
| /// Returns a non null [_NavigationDrawerDestinationInfo]. |
| /// |
| /// This will return an error if called with no [_NavigationDrawerDestinationInfo] |
| /// ancestor. |
| /// |
| /// Used by widgets that are implementing a navigation destination info to |
| /// get information like the selected animation and destination number. |
| static _NavigationDrawerDestinationInfo of(BuildContext context) { |
| final _NavigationDrawerDestinationInfo? result = context.dependOnInheritedWidgetOfExactType<_NavigationDrawerDestinationInfo>(); |
| assert( |
| result != null, |
| 'Navigation destinations need a _NavigationDrawerDestinationInfo parent, ' |
| 'which is usually provided by NavigationDrawer.', |
| ); |
| return result!; |
| } |
| |
| @override |
| bool updateShouldNotify(_NavigationDrawerDestinationInfo oldWidget) { |
| return index != oldWidget.index |
| || totalNumberOfDestinations != oldWidget.totalNumberOfDestinations |
| || selectedAnimation != oldWidget.selectedAnimation |
| || onTap != oldWidget.onTap; |
| } |
| } |
| |
| // Builder widget for widgets that need to be animated from 0 (unselected) to |
| // 1.0 (selected). |
| // |
| // This widget creates and manages an [AnimationController] that it passes down |
| // to the child through the [builder] function. |
| // |
| // When [isSelected] is `true`, the animation controller will animate from |
| // 0 to 1 (for [duration] time). |
| // |
| // When [isSelected] is `false`, the animation controller will animate from |
| // 1 to 0 (for [duration] time). |
| // |
| // If [isSelected] is updated while the widget is animating, the animation will |
| // be reversed until it is either 0 or 1 again. |
| // |
| // Usage: |
| // ```dart |
| // _SelectableAnimatedBuilder( |
| // isSelected: _isDrawerOpen, |
| // builder: (context, animation) { |
| // return AnimatedIcon( |
| // icon: AnimatedIcons.menu_arrow, |
| // progress: animation, |
| // semanticLabel: 'Show menu', |
| // ); |
| // } |
| // ) |
| // ``` |
| class _SelectableAnimatedBuilder extends StatefulWidget { |
| /// Builds and maintains an [AnimationController] that will animate from 0 to |
| /// 1 and back depending on when [isSelected] is true. |
| const _SelectableAnimatedBuilder({ |
| required this.isSelected, |
| this.duration = const Duration(milliseconds: 200), |
| required this.builder, |
| }); |
| |
| /// When true, the widget will animate an animation controller from 0 to 1. |
| /// |
| /// The animation controller is passed to the child widget through [builder]. |
| final bool isSelected; |
| |
| /// How long the animation controller should animate for when [isSelected] is |
| /// updated. |
| /// |
| /// If the animation is currently running and [isSelected] is updated, only |
| /// the [duration] left to finish the animation will be run. |
| final Duration duration; |
| |
| /// Builds the child widget based on the current animation status. |
| /// |
| /// When [isSelected] is updated to true, this builder will be called and the |
| /// animation will animate up to 1. When [isSelected] is updated to |
| /// `false`, this will be called and the animation will animate down to 0. |
| final Widget Function(BuildContext, Animation<double>) builder; |
| |
| /// |
| @override |
| _SelectableAnimatedBuilderState createState() => _SelectableAnimatedBuilderState(); |
| } |
| |
| /// State that manages the [AnimationController] that is passed to |
| /// [_SelectableAnimatedBuilder.builder]. |
| class _SelectableAnimatedBuilderState extends State<_SelectableAnimatedBuilder> |
| with SingleTickerProviderStateMixin { |
| late AnimationController _controller; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _controller = AnimationController(vsync: this); |
| _controller.duration = widget.duration; |
| _controller.value = widget.isSelected ? 1.0 : 0.0; |
| } |
| |
| @override |
| void didUpdateWidget(_SelectableAnimatedBuilder oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.duration != widget.duration) { |
| _controller.duration = widget.duration; |
| } |
| if (oldWidget.isSelected != widget.isSelected) { |
| if (widget.isSelected) { |
| _controller.forward(); |
| } else { |
| _controller.reverse(); |
| } |
| } |
| } |
| |
| @override |
| void dispose() { |
| _controller.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return widget.builder( |
| context, |
| _controller, |
| ); |
| } |
| } |
| |
| /// Returns `true` if this animation is ticking forward, or has completed, |
| /// based on [status]. |
| bool _isForwardOrCompleted(Animation<double> animation) { |
| return animation.status == AnimationStatus.forward || animation.status == AnimationStatus.completed; |
| } |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - NavigationDrawer |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| class _NavigationDrawerDefaultsM3 extends NavigationDrawerThemeData { |
| _NavigationDrawerDefaultsM3(this.context) |
| : super( |
| elevation: 1.0, |
| tileHeight: 56.0, |
| indicatorShape: const StadiumBorder(), |
| indicatorSize: const Size(336.0, 56.0), |
| ); |
| |
| final BuildContext context; |
| late final ColorScheme _colors = Theme.of(context).colorScheme; |
| late final TextTheme _textTheme = Theme.of(context).textTheme; |
| |
| @override |
| Color? get backgroundColor => _colors.surface; |
| |
| @override |
| Color? get surfaceTintColor => _colors.surfaceTint; |
| |
| @override |
| Color? get shadowColor => Colors.transparent; |
| |
| @override |
| Color? get indicatorColor => _colors.secondaryContainer; |
| |
| @override |
| MaterialStateProperty<IconThemeData?>? get iconTheme { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| return IconThemeData( |
| size: 24.0, |
| color: states.contains(MaterialState.selected) |
| ? _colors.onSecondaryContainer |
| : _colors.onSurfaceVariant, |
| ); |
| }); |
| } |
| |
| @override |
| MaterialStateProperty<TextStyle?>? get labelTextStyle { |
| return MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| final TextStyle style = _textTheme.labelLarge!; |
| return style.apply( |
| color: states.contains(MaterialState.selected) |
| ? _colors.onSecondaryContainer |
| : _colors.onSurfaceVariant, |
| ); |
| }); |
| } |
| } |
| |
| // END GENERATED TOKEN PROPERTIES - NavigationDrawer |