-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Extract cupertino page transition out of material #9059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| // but not while being controlled by a gesture. | ||
| return new SlideTransition( | ||
| position: listenable, | ||
| child: new Material( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:(
So I only pulled in the one file for this. Perhaps I should attempt to somehow just use widget.PhysicalModel here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CupertinoPageTransition should not include this Material widget. Instead, we should change MaterialPageRoute to supply the Material widget as the child for CupertinoPageTransition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should use PhysicalModel. If we look at the native iOS behaviour carefully, I think this has to know about the elevation since it actually animates the elevation too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed Material, used PhysicalModel.
Updated the elevation a bit since it looked too small vs the native look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iOS isn't based on a physical model. If there's some sort of shadow-casting object, then we should have a cupertino concept for it.
In the near term, I'd leave it out have the transition object just manage the motion design and have the visual design handled separately.
| @@ -0,0 +1,142 @@ | |||
| import 'package:flutter/widgets.dart'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing copyright block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, done.
| /// Flutter widgets implementing base Android UI components. | ||
| /// | ||
| /// To use, import `package:flutter/mountain_view.dart`. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably add some more docs here explaining how this is different from material. Also, remove the blank line between the docs and the library directive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| import 'package:flutter/widgets.dart'; | ||
| import 'package:meta/meta.dart'; | ||
|
|
||
| import '../material/material.dart'; // non ideal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't import this library. Cupertino isn't allowed to depend on Material.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| return new _CupertinoPageTransition( | ||
| return new CupertinoPageTransition( | ||
| animation: new AnimationMean(left: animation, right: forwardAnimation), | ||
| child: child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e., child: new Material(elevation: 8, child: child)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit worried that the background color on the transition will cause a flash if the page doesn't paint an opaque background, but maybe it is required to do that anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, missed a comment. Wish github would just show review notes in chronological order. You're referring to this right? #9059 (comment)
I think I'm still favourable to just leaving holes in the route canvas and letting the user see into a shadow. But I think it won't 'flash' at the beginning of the transition since everything is offscreen for iOS and for Android opacity is 0. Was that what you meant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well to be very detailed, there is a single frame flash of the offscreen canvas leaking shadow into the screen. But that's going to go away too as we increase fidelity with the native look (where even the elevation animates from right to left).
| @@ -0,0 +1,32 @@ | |||
| import 'package:flutter/widgets.dart'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing license block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
Can you talk more about the purpose of the mountain_view library vs the material library? |
|
|
||
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| /// A page route transition used for Android and Fuchsia to transition between pages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice it doesn't seem likely this will be used in fuchsia.
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| /// A page route transition used for Android and Fuchsia to transition between pages. | ||
| class MountainViewPageTransition extends AnimatedWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to me like it's just a non-hero Material page transition. (You can tell because it uses fastOutSlowIn, which is a Material curve). I don't really see the point of having this in its own library.
| // found in the LICENSE file. | ||
|
|
||
| import 'package:flutter/widgets.dart'; | ||
| import 'package:meta/meta.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
never import meta, instead import flutter/foundation
| position: listenable, | ||
| child: new PhysicalModel( | ||
| shape: BoxShape.rectangle, | ||
| color: const Color(0x00000000), // we don't need the canvas background, just the elevation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems dubious. What colour should be painted here? If the material is entirely transparent, then what's the shadow coming from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I fully understand but it seems to work. I suspect the shadow is being casted by its children which is the behaviour we want I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shadow is cast by the PhysicalModel rectangle.
This code seems wrong to me. I don't understand why the background would be explicitly given but be transparent. That's almost always a sign that something is wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color is a required field.
I looked a bit into the code and tested it on a sample app. Seems like the shadow is always drawn black regardless of the PhysicalModel's color or its child's color in https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/object.dart#L463 which seems like the desired behaviour.
I think one place where the color here might matter is if somehow the supplied child isn't a rectangle or has holes in it but I think seeing through the model onto the shadow behind it might be a clearer way to signal to the user that he/she needs to deal with the elevated hole than 'filling' it with some arbitrary color.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are filling it with an arbitrary colour. :-)
I think if we want the background to be gray, we should set it to gray. I think, though, that iOS probably has an opinion of what the default colour should be here, probably some off-white, and we should use that. Consider if someone's route is just a Text widget. It would look weird to have a shadow, but not have a background. If we want a shadow, we should commit to a background, IMHO.
cc @jason-simmons who may be able to comment on the PhysicalModel API requiring a colour.
cc @abarth who may have opinions as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, added in standard iOS background color
|
(this will need tests) |
The mountain_view library contains building blocks that are specific to Android but not specific to Material Design. For example, if we had a holo library, it would depend on mountain_view but not material. |
|
Does holo do the animate-from-the-bottom-using-fastOutSlowIn thing? What else would be common between Material and Holo that wouldn't also be common to Cupertino? |
|
Also if it's specific to Android (as opposed to ChromeOS or Andromeda or Fuchsia) then should we call it android/ rather than mountain_view/? |
|
@Hixie it's partly in #8410 and #8726 but it's mainly to distinguish between the [here's the strong opinion about how we think an full app experience should look like and probably the main part of the value add if you're trying to brand your own custom UI and by the way, here's a default one] vs [here's such basic platform specific building blocks for making an interface that matches your platform that you'll surprise users if you depart too much from it and is probably not a value add for your app to recreate]. Cupertino is currently the latter and Material is currently both which conceptually makes it strange for full customer experience developers who want to fully reject the Material app look and feel from the way they understand it (splashes, FABs, pastel color palettes) to have to import material.dart and have a MaterialApp root widget or tell them to rewrite all activity indicators etc which want a material ancestor. I guess it's a bit harder on Android since there hasn't been non Material system UI since 5.0 but the package is also good discipline to make us more consciously decide whether something we add should be reusable by custom designs or not by choosing the package to go into. Whether to call it android vs mountain_view is a good point. I'm not too sure. Would fuschia allow full custom non-material apps? If so and the platform is meant to generally behave different like use a pinch out gesture to close pages etc then maybe we should call this android. |
Yes. For example, moterm is one such app. |
I'm not sure about the exact curve, but yes Holo did animate from the bottom.
In principle, the ClampingScrollPhysics and GlowingOverscrollIndicator should be there, but that causes layering issues. We could also factor out the interactive design from the drawer and it put there. (Android calls this the ViewDragHelper.) I guess the pattern is a bunch of interaction/motion design that has been common through the various visual designs on Android. |
This applies to iOS as well, so if we factor it out I guess it should go at the widgets layer. I'm skeptical that there's enough here to justify an entire library. A library has a high cognitive cost -- it appears in all our docs in the layer diagram, it requires users to think about what they're importing, etc. I'm not even sure that the "fly in from the bottom" feature is Android-only. Wouldn't there be cases where it's reasonable on iOS? (For example, pages that require explicit confirmation and can't be swiped away?) |
|
Ok, I removed the mountain_view package as discussed and added page transition routing test. Note the transition asserted in the test is still the incorrect behaviour. This PR is just a refactor + a test for existing behaviour. |
| end: -FractionalOffset.topRight | ||
| ); | ||
|
|
||
| CupertinoPageTransition({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: constructor first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean above the static final? Done
| CupertinoPageTransition({ | ||
| Key key, | ||
| Animation<double> animation, | ||
| this.child |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: trailing commas everywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| class _CupertinoTransitionCurve extends Curve { | ||
| _CupertinoTransitionCurve(this.curve); | ||
|
|
||
| Curve curve; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| @override | ||
| void dispose() { | ||
| controller.removeStatusListener(handleStatusChanged); | ||
| controller = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if there's no particular reason to set it to null, we should skip that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| navigator.pop(); | ||
| assert(controller == null); | ||
| } else if (status == AnimationStatus.completed) { | ||
| dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self-destructing objects are scary. Who owns this object?
We should document the lifecycle of this object in detail now that we're making it public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, it's a bit strange. Seems like the child owns itself by latching itself onto the parent's addStatusListener.
I guess something more conventional would be to have the parent have a closure listening to its own controller field's status and dispose the back gesture controller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved lifecycle ownership to parent
| } | ||
|
|
||
| void handleStatusChanged(AnimationStatus status) { | ||
| if (status == AnimationStatus.dismissed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no braces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| return _backGestureController; | ||
| } | ||
|
|
||
| void handleBackGestureEnded (AnimationStatus status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extraneous space before (
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops
| CupertinoBackGestureController({ | ||
| @required NavigatorState navigator, | ||
| @required this.controller, | ||
| @required this.onDisposed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you don't need the onDisposed any more since you call it yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah thanks
# Conflicts: # packages/flutter/test/material/page_test.dart
|
Gentle ping on this. It's a previous step for #9138 |
| const double _kMinFlingVelocity = 1.0; // screen width per second. | ||
| const Color _kBackgroundColor = const Color(0xFFEFEFF4); // iOS 10 background color. | ||
|
|
||
| // Used for iOS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have some dartdocs for this class now that it's public, but we can do that in another round if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes. Done
| class CupertinoPageTransition extends AnimatedWidget { | ||
| CupertinoPageTransition({ | ||
| Key key, | ||
| Animation<double> animation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done.
| static final FractionalOffsetTween _kTween = new FractionalOffsetTween( | ||
| begin: FractionalOffset.topRight, | ||
| end: -FractionalOffset.topRight, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually make these file-level statics for no particular reason. This is fine too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is moved in the next PR. Though probably clearer inside the class. There will be 2 classes in the next PR with their own tween.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I remembered wrong. Will move to top level for consistency in next PR.
| return (status == AnimationStatus.reverse || status == AnimationStatus.dismissed); | ||
| } | ||
|
|
||
| void handleStatusChanged(AnimationStatus status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this function be private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in the next PR.
|
|
||
| // This class responds to drag gestures to control the route's transition | ||
| // animation progress. Used for iOS back gesture. | ||
| class CupertinoBackGestureController extends NavigationGestureController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dartdocs would be better now that this class is public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| return _backGestureController; | ||
| } | ||
|
|
||
| void handleBackGestureEnded(AnimationStatus status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should be private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done.

For #8410 and #8726 step 1.
Moved page transitions from material to mountain view and cupertino. No logic change.