Fix NavigationBar
indicator ripple doesn't account for label height (#117473)
This commit is contained in:
parent
589f2eb9e2
commit
74645b43aa
@ -462,26 +462,17 @@ class _NavigationDestinationBuilder extends StatelessWidget {
|
|||||||
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
|
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
|
||||||
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
|
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
|
||||||
final NavigationBarThemeData defaults = _defaultsFor(context);
|
final NavigationBarThemeData defaults = _defaultsFor(context);
|
||||||
|
final GlobalKey labelKey = GlobalKey();
|
||||||
|
|
||||||
final bool selected = info.selectedIndex == info.index;
|
final bool selected = info.selectedIndex == info.index;
|
||||||
final double labelPadding;
|
|
||||||
switch (info.labelBehavior) {
|
|
||||||
case NavigationDestinationLabelBehavior.alwaysShow:
|
|
||||||
labelPadding = 8;
|
|
||||||
break;
|
|
||||||
case NavigationDestinationLabelBehavior.onlyShowSelected:
|
|
||||||
labelPadding = selected ? 8 : 0;
|
|
||||||
break;
|
|
||||||
case NavigationDestinationLabelBehavior.alwaysHide:
|
|
||||||
labelPadding = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return _NavigationBarDestinationSemantics(
|
return _NavigationBarDestinationSemantics(
|
||||||
child: _NavigationBarDestinationTooltip(
|
child: _NavigationBarDestinationTooltip(
|
||||||
message: tooltip ?? label,
|
message: tooltip ?? label,
|
||||||
child: _IndicatorInkWell(
|
child: _IndicatorInkWell(
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
labelPadding: labelPadding,
|
labelKey: labelKey,
|
||||||
|
labelBehavior: info.labelBehavior,
|
||||||
|
selected: selected,
|
||||||
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
|
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
|
||||||
onTap: info.onTap,
|
onTap: info.onTap,
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -489,6 +480,7 @@ class _NavigationDestinationBuilder extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _NavigationBarDestinationLayout(
|
child: _NavigationBarDestinationLayout(
|
||||||
icon: buildIcon(context),
|
icon: buildIcon(context),
|
||||||
|
labelKey: labelKey,
|
||||||
label: buildLabel(context),
|
label: buildLabel(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -503,7 +495,9 @@ class _NavigationDestinationBuilder extends StatelessWidget {
|
|||||||
class _IndicatorInkWell extends InkResponse {
|
class _IndicatorInkWell extends InkResponse {
|
||||||
const _IndicatorInkWell({
|
const _IndicatorInkWell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.labelPadding,
|
required this.labelKey,
|
||||||
|
required this.labelBehavior,
|
||||||
|
required this.selected,
|
||||||
super.customBorder,
|
super.customBorder,
|
||||||
super.onTap,
|
super.onTap,
|
||||||
super.child,
|
super.child,
|
||||||
@ -512,10 +506,26 @@ class _IndicatorInkWell extends InkResponse {
|
|||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
|
||||||
final double labelPadding;
|
final GlobalKey labelKey;
|
||||||
|
final NavigationDestinationLabelBehavior labelBehavior;
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RectCallback? getRectCallback(RenderBox referenceBox) {
|
RectCallback? getRectCallback(RenderBox referenceBox) {
|
||||||
|
final RenderBox labelBox = labelKey.currentContext!.findRenderObject()! as RenderBox;
|
||||||
|
final Rect labelRect = labelBox.localToGlobal(Offset.zero) & labelBox.size;
|
||||||
|
final double labelPadding;
|
||||||
|
switch (labelBehavior) {
|
||||||
|
case NavigationDestinationLabelBehavior.alwaysShow:
|
||||||
|
labelPadding = labelRect.height / 2;
|
||||||
|
break;
|
||||||
|
case NavigationDestinationLabelBehavior.onlyShowSelected:
|
||||||
|
labelPadding = selected ? labelRect.height / 2 : 0;
|
||||||
|
break;
|
||||||
|
case NavigationDestinationLabelBehavior.alwaysHide:
|
||||||
|
labelPadding = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
final double indicatorOffsetX = referenceBox.size.width / 2;
|
final double indicatorOffsetX = referenceBox.size.width / 2;
|
||||||
final double indicatorOffsetY = referenceBox.size.height / 2 - labelPadding;
|
final double indicatorOffsetY = referenceBox.size.height / 2 - labelPadding;
|
||||||
|
|
||||||
@ -765,6 +775,7 @@ class _NavigationBarDestinationLayout extends StatelessWidget {
|
|||||||
/// 3 [NavigationBar].
|
/// 3 [NavigationBar].
|
||||||
const _NavigationBarDestinationLayout({
|
const _NavigationBarDestinationLayout({
|
||||||
required this.icon,
|
required this.icon,
|
||||||
|
required this.labelKey,
|
||||||
required this.label,
|
required this.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -773,6 +784,11 @@ class _NavigationBarDestinationLayout extends StatelessWidget {
|
|||||||
/// See [NavigationDestination.icon].
|
/// See [NavigationDestination.icon].
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
|
|
||||||
|
/// The global key for the label of this destination.
|
||||||
|
///
|
||||||
|
/// This is used to determine the position of the label relative to the icon.
|
||||||
|
final GlobalKey labelKey;
|
||||||
|
|
||||||
/// The label widget that sits below the icon.
|
/// The label widget that sits below the icon.
|
||||||
///
|
///
|
||||||
/// This widget will sometimes be faded out, depending on
|
/// This widget will sometimes be faded out, depending on
|
||||||
@ -782,7 +798,6 @@ class _NavigationBarDestinationLayout extends StatelessWidget {
|
|||||||
final Widget label;
|
final Widget label;
|
||||||
|
|
||||||
static final Key _iconKey = UniqueKey();
|
static final Key _iconKey = UniqueKey();
|
||||||
static final Key _labelKey = UniqueKey();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -806,7 +821,7 @@ class _NavigationBarDestinationLayout extends StatelessWidget {
|
|||||||
alwaysIncludeSemantics: true,
|
alwaysIncludeSemantics: true,
|
||||||
opacity: animation,
|
opacity: animation,
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
key: _labelKey,
|
key: labelKey,
|
||||||
child: label,
|
child: label,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
||||||
|
// machines.
|
||||||
|
@Tags(<String>['reduced-test-set'])
|
||||||
|
library;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -562,21 +567,26 @@ void main() {
|
|||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
|
|
||||||
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
||||||
return _buildWidget(
|
return MaterialApp(
|
||||||
NavigationBar(
|
theme: ThemeData(useMaterial3: true),
|
||||||
selectedIndex: selectedIndex,
|
home: Scaffold(
|
||||||
labelBehavior: labelBehavior,
|
bottomNavigationBar: Center(
|
||||||
destinations: const <Widget>[
|
child: NavigationBar(
|
||||||
NavigationDestination(
|
selectedIndex: selectedIndex,
|
||||||
icon: Icon(Icons.ac_unit),
|
labelBehavior: labelBehavior,
|
||||||
label: 'AC',
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.ac_unit),
|
||||||
|
label: 'AC',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.access_alarm),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
),
|
||||||
icon: Icon(Icons.access_alarm),
|
|
||||||
label: 'Alarm',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onDestinationSelected: (int i) { },
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -589,7 +599,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||||
Offset indicatorCenter = const Offset(600, 32);
|
Offset indicatorCenter = const Offset(600, 30);
|
||||||
const Size includedIndicatorSize = Size(64, 32);
|
const Size includedIndicatorSize = Size(64, 32);
|
||||||
const Size excludedIndicatorSize = Size(74, 40);
|
const Size excludedIndicatorSize = Size(74, 40);
|
||||||
|
|
||||||
@ -715,7 +725,7 @@ void main() {
|
|||||||
selectedIndex = 1;
|
selectedIndex = 1;
|
||||||
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
indicatorCenter = const Offset(600, 32);
|
indicatorCenter = const Offset(600, 30);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
inkFeatures,
|
inkFeatures,
|
||||||
@ -753,6 +763,63 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigation indicator ripple golden test', (WidgetTester tester) async {
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/issues/117420.
|
||||||
|
|
||||||
|
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true),
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: Center(
|
||||||
|
child: NavigationBar(
|
||||||
|
labelBehavior: labelBehavior,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'AC',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysShow_m3.png'));
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysHide_m3.png'));
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_onlyShowSelected_selected_m3.png'));
|
||||||
|
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_onlyShowSelected_unselected_m3.png'));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Navigation indicator scale transform', (WidgetTester tester) async {
|
testWidgets('Navigation indicator scale transform', (WidgetTester tester) async {
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
|
|
||||||
@ -892,6 +959,264 @@ void main() {
|
|||||||
expect(_getIndicatorDecoration(tester)?.color, color);
|
expect(_getIndicatorDecoration(tester)?.color, color);
|
||||||
expect(_getIndicatorDecoration(tester)?.shape, shape);
|
expect(_getIndicatorDecoration(tester)?.shape, shape);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigation indicator renders ripple', (WidgetTester tester) async {
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/issues/116751.
|
||||||
|
int selectedIndex = 0;
|
||||||
|
|
||||||
|
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: Center(
|
||||||
|
child: NavigationBar(
|
||||||
|
selectedIndex: selectedIndex,
|
||||||
|
labelBehavior: labelBehavior,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.ac_unit),
|
||||||
|
label: 'AC',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.access_alarm),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||||
|
Offset indicatorCenter = const Offset(600, 33);
|
||||||
|
const Size includedIndicatorSize = Size(64, 32);
|
||||||
|
const Size excludedIndicatorSize = Size(74, 40);
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
||||||
|
expect(
|
||||||
|
inkFeatures,
|
||||||
|
paints
|
||||||
|
..clipPath(
|
||||||
|
pathMatcher: isPathThat(
|
||||||
|
includes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
excludes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..circle(
|
||||||
|
x: indicatorCenter.dx,
|
||||||
|
y: indicatorCenter.dy,
|
||||||
|
radius: 35.0,
|
||||||
|
color: const Color(0x0a000000),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
indicatorCenter = const Offset(600, 40);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
inkFeatures,
|
||||||
|
paints
|
||||||
|
..clipPath(
|
||||||
|
pathMatcher: isPathThat(
|
||||||
|
includes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
excludes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..circle(
|
||||||
|
x: indicatorCenter.dx,
|
||||||
|
y: indicatorCenter.dy,
|
||||||
|
radius: 35.0,
|
||||||
|
color: const Color(0x0a000000),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.access_alarm)));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
inkFeatures,
|
||||||
|
paints
|
||||||
|
..clipPath(
|
||||||
|
pathMatcher: isPathThat(
|
||||||
|
includes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
excludes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..circle(
|
||||||
|
x: indicatorCenter.dx,
|
||||||
|
y: indicatorCenter.dy,
|
||||||
|
radius: 35.0,
|
||||||
|
color: const Color(0x0a000000),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure ripple is shifted when selectedIndex changes.
|
||||||
|
selectedIndex = 1;
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
indicatorCenter = const Offset(600, 33);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
inkFeatures,
|
||||||
|
paints
|
||||||
|
..clipPath(
|
||||||
|
pathMatcher: isPathThat(
|
||||||
|
includes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (includedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (includedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (includedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
excludes: <Offset>[
|
||||||
|
// Left center.
|
||||||
|
Offset(indicatorCenter.dx - (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Top center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy - (excludedIndicatorSize.height / 2)),
|
||||||
|
// Right center.
|
||||||
|
Offset(indicatorCenter.dx + (excludedIndicatorSize.width / 2), indicatorCenter.dy),
|
||||||
|
// Bottom center.
|
||||||
|
Offset(indicatorCenter.dx, indicatorCenter.dy + (excludedIndicatorSize.height / 2)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..circle(
|
||||||
|
x: indicatorCenter.dx,
|
||||||
|
y: indicatorCenter.dy,
|
||||||
|
radius: 35.0,
|
||||||
|
color: const Color(0x0a000000),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigation indicator ripple golden test', (WidgetTester tester) async {
|
||||||
|
// This is a regression test for https://github.com/flutter/flutter/issues/117420.
|
||||||
|
|
||||||
|
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: false),
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: Center(
|
||||||
|
child: NavigationBar(
|
||||||
|
labelBehavior: labelBehavior,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'AC',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysShow` (default).
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysShow_m2.png'));
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.alwaysHide`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.alwaysHide));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_alwaysHide_m2.png'));
|
||||||
|
|
||||||
|
// Test ripple when NavigationBar is using `NavigationDestinationLabelBehavior.onlyShowSelected`.
|
||||||
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).first));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_onlyShowSelected_selected_m2.png'));
|
||||||
|
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_onlyShowSelected_unselected_m2.png'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
||||||
|
// machines.
|
||||||
|
@Tags(<String>['reduced-test-set'])
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -161,6 +167,48 @@ void main() {
|
|||||||
expect(_barMaterial(tester).elevation, elevation);
|
expect(_barMaterial(tester).elevation, elevation);
|
||||||
expect(_labelBehavior(tester), labelBehavior);
|
expect(_labelBehavior(tester), labelBehavior);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Custom label style renders ink ripple properly', (WidgetTester tester) async {
|
||||||
|
Widget buildWidget({ NavigationDestinationLabelBehavior? labelBehavior }) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(
|
||||||
|
navigationBarTheme: const NavigationBarThemeData(
|
||||||
|
labelTextStyle: MaterialStatePropertyAll<TextStyle>(
|
||||||
|
TextStyle(fontSize: 25, color: Color(0xff0000ff)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: Center(
|
||||||
|
child: NavigationBar(
|
||||||
|
labelBehavior: labelBehavior,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'AC',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: SizedBox(),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
await gesture.addPointer();
|
||||||
|
await gesture.moveTo(tester.getCenter(find.byType(NavigationDestination).last));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_custom_label_style.png'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NavigationDestination> _destinations() {
|
List<NavigationDestination> _destinations() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user