mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Improve iOS fidelity of barrierColors and edge decorations for full-screen Cupertino page transitions (#95537)
This commit is contained in:
parent
06f08e4a81
commit
1ab73b15a2
@ -25,6 +25,19 @@ const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
|
||||
// user releases a page mid swipe.
|
||||
const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
|
||||
|
||||
/// Barrier color used for a barrier visible during transitions for Cupertino
|
||||
/// page routes.
|
||||
///
|
||||
/// This barrier color is only used for full-screen page routes with
|
||||
/// `fullscreenDialog: false`.
|
||||
///
|
||||
/// By default, `fullscreenDialog` Cupertino route transitions have no
|
||||
/// `barrierColor`, and [CupertinoDialogRoute]s and [CupertinoModalPopupRoute]s
|
||||
/// have a `barrierColor` defined by [kCupertinoModalBarrierColor].
|
||||
///
|
||||
/// A relatively rigorous eyeball estimation.
|
||||
const Color _kCupertinoPageTransitionBarrierColor = Color(0x18000000);
|
||||
|
||||
/// Barrier color for a Cupertino modal barrier.
|
||||
///
|
||||
/// Extracted from https://developer.apple.com/design/resources/.
|
||||
@ -126,7 +139,7 @@ mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
|
||||
Duration get transitionDuration => const Duration(milliseconds: 400);
|
||||
|
||||
@override
|
||||
Color? get barrierColor => null;
|
||||
Color? get barrierColor => fullscreenDialog ? null : _kCupertinoPageTransitionBarrierColor;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => null;
|
||||
@ -791,8 +804,6 @@ class _CupertinoEdgeShadowDecoration extends Decoration {
|
||||
end: const _CupertinoEdgeShadowDecoration._(
|
||||
// Eyeballed gradient used to mimic a drop shadow on the start side only.
|
||||
<Color>[
|
||||
Color(0x38000000),
|
||||
Color(0x12000000),
|
||||
Color(0x04000000),
|
||||
Color(0x00000000),
|
||||
],
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
@ -937,6 +938,170 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
group('Cupertino page transitions', () {
|
||||
CupertinoPageRoute<void> buildRoute({required bool fullscreenDialog}) {
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: fullscreenDialog,
|
||||
builder: (_) => const SizedBox(),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('when route is not fullscreenDialog, it has a barrierColor', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SizedBox.expand(),
|
||||
),
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
||||
buildRoute(fullscreenDialog: false),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, const Color(0x18000000));
|
||||
});
|
||||
|
||||
testWidgets('when route is a fullscreenDialog, it has no barrierColor', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SizedBox.expand(),
|
||||
),
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
||||
buildRoute(fullscreenDialog: true),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, isNull);
|
||||
});
|
||||
|
||||
testWidgets('when route is not fullscreenDialog, it has a _CupertinoEdgeShadowDecoration', (WidgetTester tester) async {
|
||||
PaintPattern paintsShadowRect({required double dx, required Color color}) {
|
||||
return paints..everything((Symbol methodName, List<dynamic> arguments) {
|
||||
if (methodName != #drawRect)
|
||||
return true;
|
||||
final Rect rect = arguments[0] as Rect;
|
||||
final Color paintColor = (arguments[1] as Paint).color;
|
||||
if (rect.top != 0 || rect.width != 1.0 || rect.height != 600)
|
||||
// _CupertinoEdgeShadowDecoration draws the shadows with a series of
|
||||
// differently colored 1px-wide rects. Skip rects that aren't being
|
||||
// drawn by the _CupertinoEdgeShadowDecoration.
|
||||
return true;
|
||||
if ((rect.left - dx).abs() >= 1)
|
||||
// Skip calls for rects until the one with the given position offset
|
||||
return true;
|
||||
if (paintColor.value == color.value)
|
||||
return true;
|
||||
throw '''
|
||||
For a rect with an expected left-side position: $dx (drawn at ${rect.left}):
|
||||
Expected a rect with color: $color,
|
||||
And drew a rect with color: $paintColor.
|
||||
''';
|
||||
});
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SizedBox.expand(),
|
||||
),
|
||||
);
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
||||
buildRoute(fullscreenDialog: false),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
|
||||
final RenderBox box = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
|
||||
// Animation starts with effectively no shadow
|
||||
expect(box, paintsShadowRect(dx: 795, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 785, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 775, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 765, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 755, color: const Color(0x00000000)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
// Part-way through the transition, the shadow is approaching the full gradient
|
||||
expect(box, paintsShadowRect(dx: 296, color: const Color(0x03000000)));
|
||||
expect(box, paintsShadowRect(dx: 286, color: const Color(0x02000000)));
|
||||
expect(box, paintsShadowRect(dx: 276, color: const Color(0x01000000)));
|
||||
expect(box, paintsShadowRect(dx: 266, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 266, color: const Color(0x00000000)));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// At the end of the transition, the shadow is a gradient between
|
||||
// 0x04000000 and 0x00000000 and is now offscreen
|
||||
expect(box, paintsShadowRect(dx: -1, color: const Color(0x04000000)));
|
||||
expect(box, paintsShadowRect(dx: -10, color: const Color(0x03000000)));
|
||||
expect(box, paintsShadowRect(dx: -20, color: const Color(0x02000000)));
|
||||
expect(box, paintsShadowRect(dx: -30, color: const Color(0x01000000)));
|
||||
expect(box, paintsShadowRect(dx: -40, color: const Color(0x00000000)));
|
||||
|
||||
// Start animation in reverse
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
expect(box, paintsShadowRect(dx: 498, color: const Color(0x04000000)));
|
||||
expect(box, paintsShadowRect(dx: 488, color: const Color(0x03000000)));
|
||||
expect(box, paintsShadowRect(dx: 478, color: const Color(0x02000000)));
|
||||
expect(box, paintsShadowRect(dx: 468, color: const Color(0x01000000)));
|
||||
expect(box, paintsShadowRect(dx: 458, color: const Color(0x00000000)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
|
||||
// At the end of the animation, the shadow approaches full transparency
|
||||
expect(box, paintsShadowRect(dx: 794, color: const Color(0x01000000)));
|
||||
expect(box, paintsShadowRect(dx: 784, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 774, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 764, color: const Color(0x00000000)));
|
||||
expect(box, paintsShadowRect(dx: 754, color: const Color(0x00000000)));
|
||||
});
|
||||
|
||||
testWidgets('when route is fullscreenDialog, it has no visible _CupertinoEdgeShadowDecoration', (WidgetTester tester) async {
|
||||
PaintPattern paintsNoShadows() {
|
||||
return paints..everything((Symbol methodName, List<dynamic> arguments) {
|
||||
if (methodName != #drawRect)
|
||||
return true;
|
||||
final Rect rect = arguments[0] as Rect;
|
||||
// _CupertinoEdgeShadowDecoration draws the shadows with a series of
|
||||
// differently colored 1px rects. Skip all rects not drawn by a
|
||||
// _CupertinoEdgeShadowDecoration.
|
||||
if (rect.width != 1.0)
|
||||
return true;
|
||||
throw '''
|
||||
Expected: no rects with a width of 1px.
|
||||
Found: $rect.
|
||||
''';
|
||||
});
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SizedBox.expand(),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderBox box = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint));
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(
|
||||
buildRoute(fullscreenDialog: true),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(box, paintsNoShadows());
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(box, paintsNoShadows());
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('ModalPopup overlay dark mode', (WidgetTester tester) async {
|
||||
late StateSetter stateSetter;
|
||||
Brightness brightness = Brightness.light;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user