Skip to content

Commit ca78327

Browse files
authored
Fix NavigationBar ripple (#115499)
* Fix `NavigationBar` ripple * remove unused import
1 parent 3a3a0db commit ca78327

File tree

2 files changed

+154
-4
lines changed

2 files changed

+154
-4
lines changed

packages/flutter/lib/src/material/navigation_bar.dart

+35-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import 'text_theme.dart';
1717
import 'theme.dart';
1818
import 'tooltip.dart';
1919

20+
const double _kIndicatorHeight = 64;
21+
const double _kIndicatorWidth = 32;
22+
2023
// Examples can assume:
2124
// late BuildContext context;
2225
// late bool _isDrawerOpen;
@@ -429,11 +432,14 @@ class _NavigationDestinationBuilder extends StatelessWidget {
429432
@override
430433
Widget build(BuildContext context) {
431434
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
435+
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
436+
final NavigationBarThemeData defaults = _defaultsFor(context);
437+
432438
return _NavigationBarDestinationSemantics(
433439
child: _NavigationBarDestinationTooltip(
434440
message: tooltip ?? label,
435-
child: InkWell(
436-
highlightColor: Colors.transparent,
441+
child: _IndicatorInkWell(
442+
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
437443
onTap: info.onTap,
438444
child: Row(
439445
children: <Widget>[
@@ -451,6 +457,31 @@ class _NavigationDestinationBuilder extends StatelessWidget {
451457
}
452458
}
453459

460+
class _IndicatorInkWell extends InkResponse {
461+
const _IndicatorInkWell({
462+
super.child,
463+
super.onTap,
464+
super.customBorder,
465+
}) : super(
466+
containedInkWell: true,
467+
highlightColor: Colors.transparent,
468+
);
469+
470+
@override
471+
RectCallback? getRectCallback(RenderBox referenceBox) {
472+
final double indicatorOffsetX = referenceBox.size.width / 2;
473+
const double indicatorOffsetY = 30.0;
474+
475+
return () {
476+
return Rect.fromCenter(
477+
center: Offset(indicatorOffsetX, indicatorOffsetY),
478+
width: _kIndicatorHeight,
479+
height: _kIndicatorWidth,
480+
);
481+
};
482+
}
483+
}
484+
454485
/// Inherited widget for passing data from the [NavigationBar] to the
455486
/// [NavigationBar.destinations] children widgets.
456487
///
@@ -562,8 +593,8 @@ class NavigationIndicator extends StatelessWidget {
562593
super.key,
563594
required this.animation,
564595
this.color,
565-
this.width = 64,
566-
this.height = 32,
596+
this.width = _kIndicatorHeight,
597+
this.height = _kIndicatorWidth,
567598
this.borderRadius = const BorderRadius.all(Radius.circular(16)),
568599
this.shape,
569600
});

packages/flutter/test/material/navigation_bar_test.dart

+119
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/gestures.dart';
67
import 'package:flutter/material.dart';
78
import 'package:flutter_test/flutter_test.dart';
89

10+
import '../rendering/mock_canvas.dart';
11+
912
void main() {
1013
testWidgets('Navigation bar updates destinations when tapped', (WidgetTester tester) async {
1114
int mutatedIndex = -1;
@@ -553,6 +556,122 @@ void main() {
553556

554557
expect(newHeight, equals(initialHeight));
555558
});
559+
560+
testWidgets('Navigation indicator renders ripple', (WidgetTester tester) async {
561+
final Widget widget = _buildWidget(
562+
NavigationBar(
563+
destinations: const <Widget>[
564+
NavigationDestination(
565+
icon: Icon(Icons.ac_unit),
566+
label: 'AC',
567+
),
568+
NavigationDestination(
569+
icon: Icon(Icons.access_alarm),
570+
label: 'Alarm',
571+
),
572+
],
573+
onDestinationSelected: (int i) {
574+
},
575+
),
576+
);
577+
578+
await tester.pumpWidget(widget);
579+
580+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
581+
await gesture.addPointer();
582+
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
583+
await tester.pumpAndSettle();
584+
585+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
586+
const Offset indicatorCenter = Offset(600, 30);
587+
const Size includedIndicatorSize = Size(64, 32);
588+
const Size excludedIndicatorSize = Size(74, 40);
589+
expect(
590+
inkFeatures,
591+
paints
592+
..clipPath(
593+
pathMatcher: isPathThat(
594+
includes: <Offset>[
595+
// Left center.
596+
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
597+
// Top center.
598+
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
599+
// Right center.
600+
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
601+
// Bottom center.
602+
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
603+
],
604+
excludes: <Offset>[
605+
// Left center.
606+
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
607+
// Top center.
608+
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
609+
// Right center.
610+
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
611+
// Bottom center.
612+
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
613+
],
614+
),
615+
)
616+
..circle(
617+
x: indicatorCenter.dx,
618+
y: indicatorCenter.dy,
619+
radius: 35.0,
620+
color: const Color(0x0a000000),
621+
)
622+
);
623+
});
624+
625+
testWidgets('Navigation indicator scale transform', (WidgetTester tester) async {
626+
int selectedIndex = 0;
627+
628+
Widget buildNavigationBar() {
629+
return MaterialApp(
630+
theme: ThemeData.light(),
631+
home: Scaffold(
632+
bottomNavigationBar: Center(
633+
child: NavigationBar(
634+
selectedIndex: selectedIndex,
635+
destinations: const <Widget>[
636+
NavigationDestination(
637+
icon: Icon(Icons.ac_unit),
638+
label: 'AC',
639+
),
640+
NavigationDestination(
641+
icon: Icon(Icons.access_alarm),
642+
label: 'Alarm',
643+
),
644+
],
645+
onDestinationSelected: (int i) { },
646+
),
647+
),
648+
),
649+
);
650+
}
651+
652+
await tester.pumpWidget(buildNavigationBar());
653+
await tester.pumpAndSettle();
654+
final Finder transformFinder = find.descendant(
655+
of: find.byType(NavigationIndicator),
656+
matching: find.byType(Transform),
657+
).last;
658+
Matrix4 transform = tester.widget<Transform>(transformFinder).transform;
659+
expect(transform.getColumn(0)[0], 0.0);
660+
661+
selectedIndex = 1;
662+
await tester.pumpWidget(buildNavigationBar());
663+
await tester.pump(const Duration(milliseconds: 100));
664+
transform = tester.widget<Transform>(transformFinder).transform;
665+
expect(transform.getColumn(0)[0], closeTo(0.7805849514007568, precisionErrorTolerance));
666+
667+
await tester.pump(const Duration(milliseconds: 100));
668+
transform = tester.widget<Transform>(transformFinder).transform;
669+
expect(transform.getColumn(0)[0], closeTo(0.9473570239543915, precisionErrorTolerance));
670+
671+
await tester.pumpAndSettle();
672+
transform = tester.widget<Transform>(transformFinder).transform;
673+
expect(transform.getColumn(0)[0], 1.0);
674+
});
556675
}
557676

558677
Widget _buildWidget(Widget child) {

0 commit comments

Comments
 (0)