mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Make Cupertino page transition elevation animated too (#9166)
* Make Cupertino page transition elevation animated too * Rename and change physical model to a decorated box * Tests * Add a comment * still need to handle null in the tween somewhere * nits * Tweens evaluate to the actual begin/end instances. Let them be non-null * Rename no decoration to none
This commit is contained in:
parent
8bf97cc42a
commit
8ed175411b
|
@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
const double _kMinFlingVelocity = 1.0; // screen width per second.
|
||||
const Color _kBackgroundColor = const Color(0xFFEFEFF4); // iOS 10 background color.
|
||||
|
||||
// Fractional offset from offscreen to the right to fully on screen.
|
||||
final FractionalOffsetTween _kRightMiddleTween = new FractionalOffsetTween(
|
||||
|
@ -26,6 +25,20 @@ 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
/// 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
|
||||
|
@ -34,51 +47,50 @@ class CupertinoPageTransition extends StatelessWidget {
|
|||
CupertinoPageTransition({
|
||||
Key key,
|
||||
// Linear route animation from 0.0 to 1.0 when this screen is being pushed.
|
||||
@required Animation<double> incomingRouteAnimation,
|
||||
@required Animation<double> primaryRouteAnimation,
|
||||
// Linear route animation from 0.0 to 1.0 when another screen is being pushed on top of this
|
||||
// one.
|
||||
@required Animation<double> outgoingRouteAnimation,
|
||||
@required Animation<double> secondaryRouteAnimation,
|
||||
@required this.child,
|
||||
// Perform incoming transition linearly. Use to precisely track back gesture drags.
|
||||
// Perform primary transition linearly. Use to precisely track back gesture drags.
|
||||
bool linearTransition,
|
||||
}) :
|
||||
_incomingPositionAnimation = linearTransition
|
||||
? _kRightMiddleTween.animate(incomingRouteAnimation)
|
||||
_primaryPositionAnimation = linearTransition
|
||||
? _kRightMiddleTween.animate(primaryRouteAnimation)
|
||||
: _kRightMiddleTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: incomingRouteAnimation,
|
||||
parent: primaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_outgoingPositionAnimation = _kMiddleLeftTween.animate(
|
||||
_secondaryPositionAnimation = _kMiddleLeftTween.animate(
|
||||
new CurvedAnimation(
|
||||
parent: outgoingRouteAnimation,
|
||||
parent: secondaryRouteAnimation,
|
||||
curve: Curves.easeOut,
|
||||
reverseCurve: Curves.easeIn,
|
||||
)
|
||||
),
|
||||
_primaryShadowAnimation = _kShadowTween.animate(primaryRouteAnimation),
|
||||
super(key: key);
|
||||
|
||||
// When this page is coming in to cover another page.
|
||||
final Animation<FractionalOffset> _incomingPositionAnimation;
|
||||
final Animation<FractionalOffset> _primaryPositionAnimation;
|
||||
// When this page is becoming covered by another page.
|
||||
final Animation<FractionalOffset> _outgoingPositionAnimation;
|
||||
final Animation<FractionalOffset> _secondaryPositionAnimation;
|
||||
final Animation<Decoration> _primaryShadowAnimation;
|
||||
final Widget child;
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO(ianh): tell the transform to be un-transformed for hit testing
|
||||
// but not while being controlled by a gesture.
|
||||
return new SlideTransition(
|
||||
position: _outgoingPositionAnimation,
|
||||
position: _secondaryPositionAnimation,
|
||||
child: new SlideTransition(
|
||||
position: _incomingPositionAnimation,
|
||||
child: new PhysicalModel(
|
||||
shape: BoxShape.rectangle,
|
||||
color: _kBackgroundColor,
|
||||
elevation: 32,
|
||||
position: _primaryPositionAnimation,
|
||||
child: new DecoratedBoxTransition(
|
||||
decoration: _primaryShadowAnimation,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -174,8 +174,8 @@ class MaterialPageRoute<T> extends PageRoute<T> {
|
|||
);
|
||||
else
|
||||
return new CupertinoPageTransition(
|
||||
incomingRouteAnimation: animation,
|
||||
outgoingRouteAnimation: secondaryAnimation,
|
||||
primaryRouteAnimation: animation,
|
||||
secondaryRouteAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
// In the middle of a back gesture drag, let the transition be linear to match finger
|
||||
// motions.
|
||||
|
|
|
@ -1078,6 +1078,9 @@ 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 ||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -63,13 +64,17 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('test iOS page transition', (WidgetTester tester) async {
|
||||
final Key page2Key = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
theme: new ThemeData(platform: TargetPlatform.iOS),
|
||||
home: new Material(child: const Text('Page 1')),
|
||||
routes: <String, WidgetBuilder>{
|
||||
'/next': (BuildContext context) {
|
||||
return new Material(child: const Text('Page 2'));
|
||||
return new Material(
|
||||
key: page2Key,
|
||||
child: const Text('Page 2'),
|
||||
);
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -79,10 +84,13 @@ void main() {
|
|||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
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];
|
||||
|
||||
// Page 1 is moving to the left.
|
||||
expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true);
|
||||
|
@ -92,6 +100,9 @@ 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);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
@ -102,6 +113,9 @@ 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'));
|
||||
|
@ -114,6 +128,9 @@ 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();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ void main() {
|
|||
expect(widget.toString, isNot(throwsException));
|
||||
});
|
||||
|
||||
group('ContainerTransition test', () {
|
||||
group('DecoratedBoxTransition test', () {
|
||||
final DecorationTween decorationTween = new DecorationTween(
|
||||
begin: new BoxDecoration(
|
||||
backgroundColor: const Color(0xFFFFFFFF),
|
||||
|
|
Loading…
Reference in a new issue