Commit b717963b authored by xster's avatar xster Committed by GitHub

Change Cupertino page transition box shadow to a simple custom gradient (#9673)

Creates another Decoration for drawing outside the decorated box with a gradient to emulate the shadow.

Lets the cupertino transition page's background be transparent.

Fixes #9321
parent acf102be
......@@ -25,20 +25,118 @@ final FractionalOffsetTween _kBottomUpTween = new FractionalOffsetTween(
end: FractionalOffset.topLeft,
);
// BoxDecoration from no shadow to page shadow mimicking iOS page transitions.
final DecorationTween _kShadowTween = new DecorationTween(
begin: BoxDecoration.none, // No shadow initially.
end: const BoxDecoration(
boxShadow: const <BoxShadow>[
const BoxShadow(
blurRadius: 10.0,
spreadRadius: 4.0,
color: const Color(0x38000000),
),
],
// Custom decoration from no shadow to page shadow mimicking iOS page
// transitions using gradients.
final DecorationTween _kGradientShadowTween = new DecorationTween(
begin: _CupertinoEdgeShadowDecoration.none, // No decoration initially.
end: const _CupertinoEdgeShadowDecoration(
edgeGradient: const LinearGradient(
// Spans 5% of the page.
begin: const FractionalOffset(0.95, 0.0),
end: FractionalOffset.topRight,
// Eyeballed gradient used to mimic a drop shadow on the left side only.
colors: const <Color>[
const Color(0x00000000),
const Color(0x04000000),
const Color(0x12000000),
const Color(0x38000000)
],
stops: const <double>[0.0, 0.3, 0.6, 1.0],
),
),
);
/// A custom [Decoration] used to paint an extra shadow on the left edge of the
/// box it's decorating. It's like a [BoxDecoration] with only a gradient except
/// it paints to the left of the box instead of behind the box.
class _CupertinoEdgeShadowDecoration extends Decoration {
const _CupertinoEdgeShadowDecoration({ this.edgeGradient });
/// A Decoration with no decorating properties.
static const _CupertinoEdgeShadowDecoration none =
const _CupertinoEdgeShadowDecoration();
/// A gradient to draw to the left of the box being decorated.
/// FractionalOffsets are relative to the original box translated one box
/// width to the left.
final LinearGradient edgeGradient;
/// Linearly interpolate between two edge shadow decorations decorations.
///
/// See also [Decoration.lerp].
static _CupertinoEdgeShadowDecoration lerp(
_CupertinoEdgeShadowDecoration a,
_CupertinoEdgeShadowDecoration b,
double t
) {
if (a == null && b == null)
return null;
return new _CupertinoEdgeShadowDecoration(
edgeGradient: LinearGradient.lerp(a?.edgeGradient, b?.edgeGradient, t),
);
}
@override
_CupertinoEdgeShadowDecoration lerpFrom(Decoration a, double t) {
if (a is! _CupertinoEdgeShadowDecoration)
return _CupertinoEdgeShadowDecoration.lerp(null, this, t);
return _CupertinoEdgeShadowDecoration.lerp(a, this, t);
}
@override
_CupertinoEdgeShadowDecoration lerpTo(Decoration b, double t) {
if (b is! _CupertinoEdgeShadowDecoration)
return _CupertinoEdgeShadowDecoration.lerp(this, null, t);
return _CupertinoEdgeShadowDecoration.lerp(this, b, t);
}
@override
_CupertinoEdgeShadowPainter createBoxPainter([VoidCallback onChanged]) {
return new _CupertinoEdgeShadowPainter(this, onChanged);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != _CupertinoEdgeShadowDecoration)
return false;
final _CupertinoEdgeShadowDecoration typedOther = other;
return edgeGradient == typedOther.edgeGradient;
}
@override
int get hashCode {
return edgeGradient.hashCode;
}
}
/// A [BoxPainter] used to draw the page transition shadow using gradients.
class _CupertinoEdgeShadowPainter extends BoxPainter {
_CupertinoEdgeShadowPainter(
@required this._decoration,
VoidCallback onChange
) : assert(_decoration != null),
super(onChange);
final _CupertinoEdgeShadowDecoration _decoration;
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final LinearGradient gradient = _decoration.edgeGradient;
if (gradient == null)
return;
// The drawable space for the gradient is a rect with the same size as
// its parent box one box width to the left of the box.
final Rect rect =
(offset & configuration.size).translate(-configuration.size.width, 0.0);
final Paint paint = new Paint()
..shader = gradient.createShader(rect);
canvas.drawRect(rect, paint);
}
}
/// Provides the native iOS page transition animation.
///
/// The page slides in from the right and exits in reverse. It also shifts to the left in
......@@ -71,7 +169,12 @@ class CupertinoPageTransition extends StatelessWidget {
reverseCurve: Curves.easeIn,
)
),
_primaryShadowAnimation = _kShadowTween.animate(primaryRouteAnimation),
_primaryShadowAnimation = _kGradientShadowTween.animate(
new CurvedAnimation(
parent: primaryRouteAnimation,
curve: Curves.easeOut,
)
),
super(key: key);
// When this page is coming in to cover another page.
......
......@@ -1129,9 +1129,6 @@ class BoxDecoration extends Decoration {
this.shape: BoxShape.rectangle
});
/// A [BoxDecoration] with no decorating properties.
static const BoxDecoration none = const BoxDecoration();
@override
bool debugAssertIsValid() {
assert(shape != BoxShape.circle ||
......
......@@ -4,7 +4,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
import '../rendering/mock_canvas.dart';
void main() {
testWidgets('test Android page transition', (WidgetTester tester) async {
......@@ -77,14 +79,14 @@ void main() {
final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(milliseconds: 150));
Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
DecoratedBox box = tester.element(find.byKey(page2Key)).ancestorWidgetOfExactType(DecoratedBox);
BoxDecoration decoration = box.decoration;
BoxShadow shadow = decoration.boxShadow[0];
final RenderDecoratedBox box = tester.element(find.byKey(page2Key))
.ancestorRenderObjectOfType(const TypeMatcher<RenderDecoratedBox>());
// Page 1 is moving to the left.
expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true);
......@@ -94,9 +96,14 @@ void main() {
expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true);
// Page 2 is coming in from the right.
expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true);
// The shadow should be exactly half its maximum extent.
expect(shadow.blurRadius, 5.0);
expect(shadow.spreadRadius, 2.0);
// The shadow should be drawn to one screen width to the left of where
// the page 2 box is. `paints` tests relative to the painter's given canvas
// rather than relative to the screen so assert that it's one screen
// width to the left of 0 offset box rect and nothing is drawn inside the
// box's rect.
expect(box, paints..rect(
rect: new Rect.fromLTWH(-800.0, 0.0, 800.0, 600.0)
));
await tester.pumpAndSettle();
......@@ -107,9 +114,6 @@ void main() {
tester.state<NavigatorState>(find.byType(Navigator)).pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
box = tester.element(find.byKey(page2Key)).ancestorWidgetOfExactType(DecoratedBox);
decoration = box.decoration;
shadow = decoration.boxShadow[0];
widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
......@@ -122,9 +126,6 @@ void main() {
expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true);
// Page 2 is leaving towards the right.
expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true);
// The shadow should be exactly 2/3 of its maximum extent.
expect(shadow.blurRadius, closeTo(6.6, 0.1));
expect(shadow.spreadRadius, closeTo(2.6, 0.1));
await tester.pumpAndSettle();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment