theme: cyanosis
1. The emergence of the problem
Since I have been testing on an Android machine before, I have not run it on iOS. Recently, FlutterUnit released the iOS version, and the most feedback received is: Swipe back
fails. At first I thought it was just the fault of WillPopScope, but I found that after jumping to many common interfaces, iOS cannot return to sliding. Then I felt very strange, there must be a demon when things go wrong, so let’s find out.
|Android Interface | iOS Interface | | — | — | | 5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfMzA=,g_se,x_0,y_0,t_100″ alt=”143.gif” style=”outline: none;”> | |
In the above iOS interface, click About Bee
to enter the interface and you can swipe back normally, but you cannot swipe back when you jump to Account Information
. Therefore, it is natural to think of checking the relevant source code location to see the difference in processing. Then you can see that About Bees
is redirected through MaterialPageRoute
, and Account Information
is redirected through Right2LeftRouter
. Then I checked several other interfaces that cannot be rolled back, and I can hammer it out:
There is a problem with the auxiliary routing class of the interface jump animation that I encapsulated before!!!
2. Jump animation about routing
Right2LeftRouter is an assistant that can jump animation from left to right when jumping interface. This was written 4 years ago, and the related articles are: Flutter Welfare | Hero Transition Component Sharing – Bonus-Route Animation Tool Class , I didn’t expect to be dug up today. We all know the effect of MaterialPageRoute jump, which is transparency + zoom animation in Android. This can be seen in the source code, the following is the logic of building a jump animation: get PageTransitionsTheme
according to the context to execute:
It can be seen that different platforms have different default animation transformation effects. For example, the android platform uses ZoomPageTransitionsBuilder
, and ios and macos use CupertinoPageTransitionsBuilder
.
Follow up and see: ZoomPageTransitionsBuilder
uses the _ZoomEnterTransition
component when entering, which defines the transparency and zoom animation. This is the root of the transparency + zoom animation jump interface on the Android platform.
In CupertinoPageTransitionsBuilder
, it is easy to see that SlideTransition
is used, that is, sliding left and right.
So XXXTransitionsBuilder can determine the interface routing transition animation, and the default value provided in PageTransitionsTheme means that we can specify the style of the platform transition animation through the theme. You can also customize XXXTransitionsBuilder to achieve your own style, this piece will be studied and sorted out in detail when you have time. Stop here, and write an article in the future. Now focus on how to modify the previous routing jump animation tool so that it can meet the requirements.
3. The twists and turns of the road to correction
The idea at the beginning (partial): In fact, it is very simple. The previous inheritance is that PageRouteBuilder
cannot fulfill the requirements, and it and MaterialPageRoute
are brothers, both of which are derived classes of PageRoute
. So just change it to inherit MaterialPageRoute
, the biggest problem is how to modify the jump animation effect of MaterialPageRoute
.
From the above we know that the core of decision jump animation is the buildTransitions
method, which is defined in MaterialRouteTransitionMixin
and mixed in by MaterialPageRoute
. So there are two ways to change the animation processing method:
Method 1, modify the transition constructor provided in PageTransitionsTheme
The second way is to inherit from MaterialPageRoute, override buildTransitions, and provide the implementation yourself.
From the perspective of coupling, the first method is better, you can customize XXXTransitionsBuilder to plug in and out at will, and you can control the effect of each platform. Here, because the previous code is an inheritance system, in order not to destroy the existing code, method 2 is also used here. The advantage of this approach is that you can access and control more details, such as the duration of the animation. Each has its own advantages and disadvantages, and the implementation is also the operation of the animator, and there is no difference in essence.
The following is the processing of jumping animation routing from right to left, overriding buildTransitions
to control the animation effect, and overriding transitionDuration
to control the duration.
“`dart //right—>left class Right2LeftRouter
extends MaterialPageRoute
{ final Widget child; final Duration duration; final Curve curve;
Right2LeftRouter({ required this.child, this.duration = const Duration(milliseconds: 300), this.curve = Curves.fastOutSlowIn, }) : super(builder: (_) => child);
@override Widget buildTransitions( BuildContext context, Animation
animation, animation
secondaryAnimation, Widget child, ) { return SlideTransition( position: Tween
( begin: const Offset(1.0, 0.0), end: const Offset(0.0, 0.0), ).animate(CurvedAnimation(parent: animation, curve: curve)), child: child, ); }
@override Duration get transitionDuration => duration; } “`
But the result is counterproductive, and iOS uses the above Right2LeftRouter
to slide back. The only difference between this and MaterialPageRoute is that buildTransitions is customized. So now all the clues point to the problem: CupertinoPageTransitionsBuilder, it must respond to the back gesture internally.
From this point of view, it is more troublesome to customize the jump transition animation of iOS. The back gesture is handled in CupertinoPageTransitionsBuilder, so the official implication is: use mine obediently, don’t mess around.
But I’m not a good kid. The default animation of iOS is to enter the page from right to left, but if you want to implement other animations such as transparency gradient entry and support gestures to fall back, it will be more troublesome. How can you catch a tiger if you don’t enter the tiger’s den, go explore the way.
4. Go deep into the tiger’s den and analyze the behavior of CupertinoPageTransitionsBuilder
As shown below, the CupertinoPageTransitionsBuilder
uses the buildTransitions
logic handled by the CupertinoRouteTransitionMixin.buildPageTransitions
static method:
If you continue to follow up, you can find that there is a _CupertinoBackGestureDetector
gesture detection component under CupertinoPageTransition. It is easy to see from the name that it handles iOS fallback gesture events. It is not difficult to see from here that the back gesture of iOS in Flutter is a component behavior, while the back gesture in Android is a system behavior.
This is why the above inherits from MaterialPageRoute
, and cannot complete the fallback effect. You have not planted a seed, of course nothing will sprout.
Let’s take a look at the specific processing of the _CupertinoBackGestureDetector
fallback detection component: there are two callback functions, both logics are completed by static methods:
enabledCallback: A function that returns a bool value, used to indicate whether a fallback is possible.
onStartPopGesture : Returns the function of_CupertinoBackGestureController
, when the back gesture is triggered.
First of all, it can be seen from the class declaration that it is a StatefulWidget
, that is to say, it needs to maintain the state quantity internally, and the focus is on the construction logic of the state class and the maintenance logic of the state quantity.
Then, get straight to the point and see what’s built into it. The following is the build method of the corresponding state class, which is not very complicated. Stack it through Stack, place a drag area through PositionedDirectional, and use Listener to listen to gesture events. The GestureDetector
is not used here, probably because it is only a horizontal drag event, and it is more straightforward to use a Listener.
As can be seen from the above, the dragged area defaults to 20
logical pixels, which is shown in the red box below. If you set a large horizontal padding for MediaQuery, the width can go beyond 20, but the application rarely sets global horizontal padding.
dart const double _kBackGestureWidth = 20.0;
5. Learn the handling of gesture events from the source code
Here you can learn how to handle horizontal drag events through Listener in the source code. Since the Listener component can only listen to the onPointerDown
event, that is, the touch point is pressed, an additional thing is needed to track the behavior track of the touch point, which is the gesture detector
. If you have read the booklet “Flutter Gesture Exploration-Controlling the World”, you may be familiar with it.
As shown below, the HorizontalDragGestureRecognizer
horizontal drag gesture detector is maintained in the state class. The gesture detector is created when the state is initialized and needs to be destroyed when dispose. This is why the component is StatefulWidget
.
After the horizontal drag gesture detector is created, the next step is to associate the detector with the touch point. This event is very obvious, that is, the Listener component listens to when the contact is pressed, as shown below. In fact, if you think about it carefully, this is no different from the logic processing of the GestureDetector component.
At this point, the logic of gesture events is very clear. HorizontalDragGestureRecognizer
detects touch points and triggers related callbacks at corresponding times, such as when dragging starts and dragging updates. What the detector provides is the data already carried by the event type. As for what reaction the interface needs to make according to the event and data, it needs to be handled by the outside world in the callback.
The handler is _CupertinoBackGestureController
, which will be created by OnStartPopGesture
callback when dragging starts, which is the _startPopGesture
static method seen above. When dragging and updating, this object is also processed through dragUpdate
. That is to say, _CupertinoBackGestureController
has the ability to change the interface shape. For example, when dragging to update, the dragUpdate method will make the interface offset. Where does its magic come from?
It can be seen from the source code that it holds an animation controller, which is very clear: the routing jump animation is essentially transformed through the animation controller. If the interface is a puppet, then the animation controller is the string that controls the puppet, that is to say, _CupertinoBackGestureController
holds the string holder .
One last note: how the lift is held by the _CupertinoBackGestureController
. In fact, it is easy to understand that some people are born with a golden key in their mouths.
At this point, all the clues to solve the case have been sorted out. Now if you want to customize the jump animation and also want iOS to support fallback, you can copy the code related to the magic animation transformation of CupertinoPageTransitionsBuilder.
6. What should be paid attention to if you want to customize animation routing animation
In fact, after looking at the source code, I know that CupertinoPageTransition
is essentially based on SlideTransition
, and several animation curves have been processed. To be honest, the effect is quite good. Then the tattered animation thing I wrote before suddenly became unsavory. As shown below, what I wrote before was a simple SlideTransition
.
In general, the animation effects of Flutter are basically enough. Think about whether it is really necessary to do some more fancy jump animations. The following are four built-in jump animations in Flutter, but only _CupertinoBackGestureDetector
handles the verification of iOS back gestures. So if you want to customize the animation effect, you must modify the implementation of CupertinoPageTransitionsBuilder
.
Let’s take a transparency gradient animation as an example. For example, click the search box on the home page in FlutterUnit, and the transparency will gradually change to jump to the search page. If you want iOS to also have transparency animation, you need to modify CupertinoPageTransitionsBuilder to handle it. For example, define a FadePageRouter
here to handle transparency gradient routing:
The following is the core code, the main thing is to take the CupertinoBackGestureDetector
over, and set it up for the child when it is on the iOS platform. In this way, iOS can handle the fallback event, the code is detailed in: fadepageroute.dart. If you want to define other animations, you can handle them in buildTransitions
according to animation.
“`dart class FadePageRoute
extends MaterialPageRoute
{ final Widget child; final Duration duration; final Curve? curve;
FadePageRoute({ required this.child, this.duration = const Duration(milliseconds: 300), this.curve, }) : super(builder: (_) => child);
@override Widget buildTransitions( BuildContext context, Animation
animation, animation
secondaryAnimation, Widget child, ) { if (Platform.isIOS) { child = CupertinoBackGestureDetector( enabledCallback: () => isPopGestureEnabled
(this), onStartPopGesture: () => startPopGesture
(this), child: child, ); }
if (curve != null) { animation = CurvedAnimation( parent: animation, curve: curve!, ); } return FadeTransition( opacity: Tween(begin: 0.1, end: 1.0).animate(animation), child: child, );
}
@override Duration get transitionDuration => duration;
} “`
This eloquent article is about to write 10,000 words. Through this exploration, I have a lot of new understanding of routing animation. It also gives a better way to customize routing animation. I hope that everyone can also learn some knowledge from the processing of the source code, instead of just taking the answer to solve the problem, but to cultivate their ability to solve the problem. WillPopScope I took a look at the source code, and I have some problems with the iOS rollback gesture. I will introduce it separately in the next article, so this article is here, thank you for watching~