@@ -346,7 +346,7 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
346
346
late List <AnimationController > _destinationControllers;
347
347
late List <Animation <double >> _destinationAnimations;
348
348
late AnimationController _extendedController;
349
- late Animation < double > _extendedAnimation;
349
+ late CurvedAnimation _extendedAnimation;
350
350
351
351
@override
352
352
void initState () {
@@ -488,6 +488,8 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
488
488
controller.dispose ();
489
489
}
490
490
_extendedController.dispose ();
491
+ _extendedAnimation.dispose ();
492
+
491
493
}
492
494
493
495
void _initControllers () {
@@ -528,8 +530,8 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
528
530
}
529
531
}
530
532
531
- class _RailDestination extends StatelessWidget {
532
- _RailDestination ({
533
+ class _RailDestination extends StatefulWidget {
534
+ const _RailDestination ({
533
535
required this .minWidth,
534
536
required this .minExtendedWidth,
535
537
required this .icon,
@@ -547,11 +549,7 @@ class _RailDestination extends StatelessWidget {
547
549
this .indicatorColor,
548
550
this .indicatorShape,
549
551
this .disabled = false ,
550
- }) : _positionAnimation = CurvedAnimation (
551
- parent: ReverseAnimation (destinationAnimation),
552
- curve: Curves .easeInOut,
553
- reverseCurve: Curves .easeInOut.flipped,
554
- );
552
+ });
555
553
556
554
final double minWidth;
557
555
final double minExtendedWidth;
@@ -571,111 +569,148 @@ class _RailDestination extends StatelessWidget {
571
569
final ShapeBorder ? indicatorShape;
572
570
final bool disabled;
573
571
574
- final Animation <double > _positionAnimation;
572
+
573
+ @override
574
+ State <_RailDestination > createState () => _RailDestinationState ();
575
+ }
576
+
577
+ class _RailDestinationState extends State <_RailDestination > {
578
+ late CurvedAnimation _positionAnimation;
579
+
580
+ @override
581
+ void initState () {
582
+ super .initState ();
583
+ _setPositionAnimation ();
584
+ }
585
+
586
+ @override
587
+ void didUpdateWidget (_RailDestination oldWidget) {
588
+ super .didUpdateWidget (oldWidget);
589
+ if (widget.destinationAnimation != oldWidget.destinationAnimation) {
590
+ _positionAnimation.dispose ();
591
+ _setPositionAnimation ();
592
+ }
593
+ }
594
+
595
+ void _setPositionAnimation () {
596
+ _positionAnimation = CurvedAnimation (
597
+ parent: ReverseAnimation (widget.destinationAnimation),
598
+ curve: Curves .easeInOut,
599
+ reverseCurve: Curves .easeInOut.flipped,
600
+ );
601
+ }
602
+
603
+ @override
604
+ void dispose () {
605
+ _positionAnimation.dispose ();
606
+ super .dispose ();
607
+ }
608
+
609
+
575
610
576
611
@override
577
612
Widget build (BuildContext context) {
578
613
assert (
579
- useIndicator || indicatorColor == null ,
614
+ widget. useIndicator || widget. indicatorColor == null ,
580
615
'[NavigationRail.indicatorColor] does not have an effect when [NavigationRail.useIndicator] is false' ,
581
616
);
582
617
583
618
final ThemeData theme = Theme .of (context);
584
619
final TextDirection textDirection = Directionality .of (context);
585
620
final bool material3 = theme.useMaterial3;
586
- final EdgeInsets destinationPadding = (padding ?? EdgeInsets .zero).resolve (textDirection);
621
+ final EdgeInsets destinationPadding = (widget. padding ?? EdgeInsets .zero).resolve (textDirection);
587
622
Offset indicatorOffset;
588
623
bool applyXOffset = false ;
589
624
590
625
final Widget themedIcon = IconTheme (
591
- data: disabled
592
- ? iconTheme.copyWith (color: theme.colorScheme.onSurface.withOpacity (0.38 ))
593
- : iconTheme,
594
- child: icon,
626
+ data: widget. disabled
627
+ ? widget. iconTheme.copyWith (color: theme.colorScheme.onSurface.withOpacity (0.38 ))
628
+ : widget. iconTheme,
629
+ child: widget. icon,
595
630
);
596
631
final Widget styledLabel = DefaultTextStyle (
597
- style: disabled
598
- ? labelTextStyle.copyWith (color: theme.colorScheme.onSurface.withOpacity (0.38 ))
599
- : labelTextStyle,
600
- child: label,
632
+ style: widget. disabled
633
+ ? widget. labelTextStyle.copyWith (color: theme.colorScheme.onSurface.withOpacity (0.38 ))
634
+ : widget. labelTextStyle,
635
+ child: widget. label,
601
636
);
602
637
603
638
Widget content;
604
639
605
640
// The indicator height is fixed and equal to _kIndicatorHeight.
606
641
// When the icon height is larger than the indicator height the indicator
607
642
// vertical offset is used to vertically center the indicator.
608
- final bool isLargeIconSize = iconTheme.size != null && iconTheme.size! > _kIndicatorHeight;
609
- final double indicatorVerticalOffset = isLargeIconSize ? (iconTheme.size! - _kIndicatorHeight) / 2 : 0 ;
643
+ final bool isLargeIconSize = widget. iconTheme.size != null && widget. iconTheme.size! > _kIndicatorHeight;
644
+ final double indicatorVerticalOffset = isLargeIconSize ? (widget. iconTheme.size! - _kIndicatorHeight) / 2 : 0 ;
610
645
611
- switch (labelType) {
646
+ switch (widget. labelType) {
612
647
case NavigationRailLabelType .none:
613
648
// Split the destination spacing across the top and bottom to keep the icon centered.
614
649
final Widget ? spacing = material3 ? const SizedBox (height: _verticalDestinationSpacingM3 / 2 ) : null ;
615
650
indicatorOffset = Offset (
616
- minWidth / 2 + destinationPadding.left,
651
+ widget. minWidth / 2 + destinationPadding.left,
617
652
_verticalDestinationSpacingM3 / 2 + destinationPadding.top + indicatorVerticalOffset,
618
653
);
619
654
final Widget iconPart = Column (
620
655
children: < Widget > [
621
656
if (spacing != null ) spacing,
622
657
SizedBox (
623
- width: minWidth,
624
- height: material3 ? null : minWidth,
658
+ width: widget. minWidth,
659
+ height: material3 ? null : widget. minWidth,
625
660
child: Center (
626
661
child: _AddIndicator (
627
- addIndicator: useIndicator,
628
- indicatorColor: indicatorColor,
629
- indicatorShape: indicatorShape,
662
+ addIndicator: widget. useIndicator,
663
+ indicatorColor: widget. indicatorColor,
664
+ indicatorShape: widget. indicatorShape,
630
665
isCircular: ! material3,
631
- indicatorAnimation: destinationAnimation,
666
+ indicatorAnimation: widget. destinationAnimation,
632
667
child: themedIcon,
633
668
),
634
669
),
635
670
),
636
671
if (spacing != null ) spacing,
637
672
],
638
673
);
639
- if (extendedTransitionAnimation.value == 0 ) {
674
+ if (widget. extendedTransitionAnimation.value == 0 ) {
640
675
content = Padding (
641
- padding: padding ?? EdgeInsets .zero,
676
+ padding: widget. padding ?? EdgeInsets .zero,
642
677
child: Stack (
643
678
children: < Widget > [
644
679
iconPart,
645
680
// For semantics when label is not showing,
646
681
SizedBox .shrink (
647
682
child: Visibility .maintain (
648
683
visible: false ,
649
- child: label,
684
+ child: widget. label,
650
685
),
651
686
),
652
687
],
653
688
),
654
689
);
655
690
} else {
656
- final Animation <double > labelFadeAnimation = extendedTransitionAnimation.drive (CurveTween (curve: const Interval (0.0 , 0.25 )));
691
+ final Animation <double > labelFadeAnimation = widget. extendedTransitionAnimation.drive (CurveTween (curve: const Interval (0.0 , 0.25 )));
657
692
applyXOffset = true ;
658
693
content = Padding (
659
- padding: padding ?? EdgeInsets .zero,
694
+ padding: widget. padding ?? EdgeInsets .zero,
660
695
child: ConstrainedBox (
661
696
constraints: BoxConstraints (
662
- minWidth: lerpDouble (minWidth, minExtendedWidth, extendedTransitionAnimation.value)! ,
697
+ minWidth: lerpDouble (widget. minWidth, widget. minExtendedWidth, widget. extendedTransitionAnimation.value)! ,
663
698
),
664
699
child: ClipRect (
665
700
child: Row (
666
701
children: < Widget > [
667
702
iconPart,
668
703
Align (
669
704
heightFactor: 1.0 ,
670
- widthFactor: extendedTransitionAnimation.value,
705
+ widthFactor: widget. extendedTransitionAnimation.value,
671
706
alignment: AlignmentDirectional .centerStart,
672
707
child: FadeTransition (
673
708
alwaysIncludeSemantics: true ,
674
709
opacity: labelFadeAnimation,
675
710
child: styledLabel,
676
711
),
677
712
),
678
- SizedBox (width: _horizontalDestinationPadding * extendedTransitionAnimation.value),
713
+ SizedBox (width: _horizontalDestinationPadding * widget. extendedTransitionAnimation.value),
679
714
],
680
715
),
681
716
),
@@ -685,42 +720,42 @@ class _RailDestination extends StatelessWidget {
685
720
case NavigationRailLabelType .selected:
686
721
final double appearingAnimationValue = 1 - _positionAnimation.value;
687
722
final double verticalPadding = lerpDouble (_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue)! ;
688
- final Interval interval = selected ? const Interval (0.25 , 0.75 ) : const Interval (0.75 , 1.0 );
689
- final Animation <double > labelFadeAnimation = destinationAnimation.drive (CurveTween (curve: interval));
690
- final double minHeight = material3 ? 0 : minWidth;
723
+ final Interval interval = widget. selected ? const Interval (0.25 , 0.75 ) : const Interval (0.75 , 1.0 );
724
+ final Animation <double > labelFadeAnimation = widget. destinationAnimation.drive (CurveTween (curve: interval));
725
+ final double minHeight = material3 ? 0 : widget. minWidth;
691
726
final Widget topSpacing = SizedBox (height: material3 ? 0 : verticalPadding);
692
727
final Widget labelSpacing = SizedBox (height: material3 ? lerpDouble (0 , _verticalIconLabelSpacingM3, appearingAnimationValue)! : 0 );
693
728
final Widget bottomSpacing = SizedBox (height: material3 ? _verticalDestinationSpacingM3 : verticalPadding);
694
729
final double indicatorHorizontalPadding = (destinationPadding.left / 2 ) - (destinationPadding.right / 2 );
695
730
final double indicatorVerticalPadding = destinationPadding.top;
696
731
indicatorOffset = Offset (
697
- minWidth / 2 + indicatorHorizontalPadding,
732
+ widget. minWidth / 2 + indicatorHorizontalPadding,
698
733
indicatorVerticalPadding + indicatorVerticalOffset,
699
734
);
700
- if (minWidth < _NavigationRailDefaultsM2 (context).minWidth! ) {
735
+ if (widget. minWidth < _NavigationRailDefaultsM2 (context).minWidth! ) {
701
736
indicatorOffset = Offset (
702
- minWidth / 2 + _horizontalDestinationSpacingM3,
737
+ widget. minWidth / 2 + _horizontalDestinationSpacingM3,
703
738
indicatorVerticalPadding + indicatorVerticalOffset,
704
739
);
705
740
}
706
741
content = Container (
707
742
constraints: BoxConstraints (
708
- minWidth: minWidth,
743
+ minWidth: widget. minWidth,
709
744
minHeight: minHeight,
710
745
),
711
- padding: padding ?? const EdgeInsets .symmetric (horizontal: _horizontalDestinationPadding),
746
+ padding: widget. padding ?? const EdgeInsets .symmetric (horizontal: _horizontalDestinationPadding),
712
747
child: ClipRect (
713
748
child: Column (
714
749
mainAxisSize: MainAxisSize .min,
715
750
mainAxisAlignment: MainAxisAlignment .center,
716
751
children: < Widget > [
717
752
topSpacing,
718
753
_AddIndicator (
719
- addIndicator: useIndicator,
720
- indicatorColor: indicatorColor,
721
- indicatorShape: indicatorShape,
754
+ addIndicator: widget. useIndicator,
755
+ indicatorColor: widget. indicatorColor,
756
+ indicatorShape: widget. indicatorShape,
722
757
isCircular: false ,
723
- indicatorAnimation: destinationAnimation,
758
+ indicatorAnimation: widget. destinationAnimation,
724
759
child: themedIcon,
725
760
),
726
761
labelSpacing,
@@ -740,37 +775,37 @@ class _RailDestination extends StatelessWidget {
740
775
),
741
776
);
742
777
case NavigationRailLabelType .all:
743
- final double minHeight = material3 ? 0 : minWidth;
778
+ final double minHeight = material3 ? 0 : widget. minWidth;
744
779
final Widget topSpacing = SizedBox (height: material3 ? 0 : _verticalDestinationPaddingWithLabel);
745
780
final Widget labelSpacing = SizedBox (height: material3 ? _verticalIconLabelSpacingM3 : 0 );
746
781
final Widget bottomSpacing = SizedBox (height: material3 ? _verticalDestinationSpacingM3 : _verticalDestinationPaddingWithLabel);
747
782
final double indicatorHorizontalPadding = (destinationPadding.left / 2 ) - (destinationPadding.right / 2 );
748
783
final double indicatorVerticalPadding = destinationPadding.top;
749
784
indicatorOffset = Offset (
750
- minWidth / 2 + indicatorHorizontalPadding,
785
+ widget. minWidth / 2 + indicatorHorizontalPadding,
751
786
indicatorVerticalPadding + indicatorVerticalOffset,
752
787
);
753
- if (minWidth < _NavigationRailDefaultsM2 (context).minWidth! ) {
788
+ if (widget. minWidth < _NavigationRailDefaultsM2 (context).minWidth! ) {
754
789
indicatorOffset = Offset (
755
- minWidth / 2 + _horizontalDestinationSpacingM3,
790
+ widget. minWidth / 2 + _horizontalDestinationSpacingM3,
756
791
indicatorVerticalPadding + indicatorVerticalOffset,
757
792
);
758
793
}
759
794
content = Container (
760
795
constraints: BoxConstraints (
761
- minWidth: minWidth,
796
+ minWidth: widget. minWidth,
762
797
minHeight: minHeight,
763
798
),
764
- padding: padding ?? const EdgeInsets .symmetric (horizontal: _horizontalDestinationPadding),
799
+ padding: widget. padding ?? const EdgeInsets .symmetric (horizontal: _horizontalDestinationPadding),
765
800
child: Column (
766
801
children: < Widget > [
767
802
topSpacing,
768
803
_AddIndicator (
769
- addIndicator: useIndicator,
770
- indicatorColor: indicatorColor,
771
- indicatorShape: indicatorShape,
804
+ addIndicator: widget. useIndicator,
805
+ indicatorColor: widget. indicatorColor,
806
+ indicatorShape: widget. indicatorShape,
772
807
isCircular: false ,
773
- indicatorAnimation: destinationAnimation,
808
+ indicatorAnimation: widget. destinationAnimation,
774
809
child: themedIcon,
775
810
),
776
811
labelSpacing,
@@ -791,15 +826,15 @@ class _RailDestination extends StatelessWidget {
791
826
: colors.primary.withOpacity (0.04 );
792
827
return Semantics (
793
828
container: true ,
794
- selected: selected,
829
+ selected: widget. selected,
795
830
child: Stack (
796
831
children: < Widget > [
797
832
Material (
798
833
type: MaterialType .transparency,
799
834
child: _IndicatorInkWell (
800
- onTap: disabled ? null : onTap,
801
- borderRadius: BorderRadius .all (Radius .circular (minWidth / 2.0 )),
802
- customBorder: indicatorShape,
835
+ onTap: widget. disabled ? null : widget. onTap,
836
+ borderRadius: BorderRadius .all (Radius .circular (widget. minWidth / 2.0 )),
837
+ customBorder: widget. indicatorShape,
803
838
splashColor: effectiveSplashColor,
804
839
hoverColor: effectiveHoverColor,
805
840
useMaterial3: material3,
@@ -810,7 +845,7 @@ class _RailDestination extends StatelessWidget {
810
845
),
811
846
),
812
847
Semantics (
813
- label: indexLabel,
848
+ label: widget. indexLabel,
814
849
),
815
850
],
816
851
),
0 commit comments