The cat said
This article is about how to add motion characteristics, motion ball, gravity, Bezier curves, polygons, irregular curves in your animation, if you are looking for this information, this source code you can have a good digestion. This is all the basics of animation. The front end is all about cool. Here we go.
The best experience is to read the original article (link below).
Old iron remember to forward, Brother Cat will present more Flutter good articles ~~~~
Ducafecat WeChat group
B stationspace.bilibili.com/404904528
The original
Preyea-regmi.medium.com/implementin…
code
Github.com/PreyeaRegmi…
reference
- Pub. Flutter – IO. Cn/packages/ge…
- The dart. Dev/guides/lang…
The body of the
Most of the time implementing motion design is a bit of a drag on mobile applications. This paper explains how to achieve motion design through Flutter from a more practical perspective. We will take a simple motion design from dribbling as a reference and start building it step by step. All copyright is reserved to the respective authors, and the full source code of the implementation can be found on Github.
Github.com/PreyeaRegmi…
Now let’s focus on the login/registration interaction. So, just like any other interaction design, we’ll try to break it down into multiple scenarios so we can have a clear overall idea and link these scenarios together.
Scenario 1: Initial status screen
In this scene, we have a bouncing image and text at the bottom, a curved white background, a brand title surrounding the center of the image and an amoeba shaped background. Drag the bottom content until a certain distance is covered, revealing the animation to play and the scene to switch to the next scene.
Show animation (middle scene)
In this intermediate scene, the curvilinear background height is animated. In addition, in this animation, the control point’s cubic Bezier curves are also shifted and restored to provide an acceleration effect. The side ICONS and amoeba background are also translated vertically in response to the animation.
Scene 2: The animation status screen is displayed later
When the display animation is complete, the brand title is replaced by a circular icon, a label indicator flies from the left side of the screen, and the corresponding label is loaded.
We now have an overview of the relevant scenarios involved in the design. Next, we try to map these ideas into implementation details. So let’s get started.
We’ll use the Stack as the top-level container to host all of our scenes, and based on the current scene state, we’ll add individual widgets to the stack and animate their geometry.
@override
Widget build(BuildContext context) {
List<Widget> stackChildren = [];
switch (currentScreenState) {
case CURRENT_SCREEN_STATE.INIT_STATE:
stackChildren.addAll(_getBgWidgets());
stackChildren.addAll(_getDefaultWidgets());
stackChildren.addAll(_getInitScreenWidgets());
stackChildren.add(_getBrandTitle());
break;
case CURRENT_SCREEN_STATE.REVEALING_ANIMATING_STATE:
stackChildren.addAll(_getBgWidgets());
stackChildren.addAll(_getDefaultWidgets());
stackChildren.add(_getBrandTitle());
break;
case CURRENT_SCREEN_STATE.POST_REVEAL_STATE:
stackChildren.addAll(_getBgWidgets());
stackChildren.addAll(_getDefaultWidgets());
stackChildren.insert(stackChildren.length - 1, _getCurvedPageSwitcher());
stackChildren.addAll(_getPostRevealAnimationStateWidgets());
stackChildren.add(buildPages());
break;
}
return Stack(children: stackChildren);
}
Copy the code
For scenario 1, all the corresponding widgets are located and added to the stack. The bouncing effect of the “slide Up start” widget at the bottom also starts immediately.
//Animation Controller for setting bounce animation for "Swipe up" text widget
_swipeUpBounceAnimationController =
AnimationController(duration: Duration(milliseconds: 800), vsync: this)
..repeat(reverse: true);
//Animation for actual bounce effect
_swipeUpBounceAnimation = Tween<double>(begin: 0, end: - 20).animate( CurvedAnimation( parent: _swipeUpBounceAnimationController, curve: Curves.easeOutBack)) .. addListener(() { setState(() { _swipeUpDy = _swipeUpBounceAnimation.value; }); });//We want to loop bounce effect until user intercepts with drag touch event.
_swipeUpBounceAnimationController.repeat(reverse: true);
//Animated value used by corresponding "Swipe up to Start" Widget in _getInitScreenWidgets() method
Positioned(
right: 0,
left: 0,
bottom: widget.height * .05,
child: Transform.translate(
offset: Offset(0, _swipeUpDy),
child: IgnorePointer(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.upload_rounded,
color: Colors.deepPurple,
size: 52,
),
Text(
"Swipe up to start",
style: TextStyle(color: Colors.grey.shade800),
)
]),
))),
Copy the code
To enable the dragging behavior of the widget, a scrollable widget is also placed at the top, covering the bottom half of the screen. The “Slide up start” is also translated according to the distance you drag. Once you cross the threshold (70% of the height of the part that can be rolled), a display animation is played.
//A simple container with a SingleChildScrollView. The trick is to set the child of SingleChildScrollView height
//exceed the height of parent scroll widget so it can be scrolled. The BouncingScrollPhysics helps the scroll retain its
//original position if it doesn't cross the threshold to play reveal animation.
//This widget is added by _getInitScreenWidgets() method
Positioned(
right: 0,
left: 0,
bottom: 0,
child: Container(
height: widget.height * . 5,
child: SingleChildScrollView(
controller: _scrollController,
physics: BouncingScrollPhysics(),
child: Container(
height: widget.height * . 5 + 1..// color:Colors.yellow,),),),),//Intercepts the bounce animation and start dragg animation
void _handleSwipe() {
_swipeUpBounceAnimationController.stop(canceled: true);
double dy = _scrollController.position.pixels;
double scrollRatio =
math.min(1.0, _scrollController.position.pixels / _swipeDistance);
//If user scroll 70% of the scrolling region we proceed towards reveal animation
if (scrollRatio > 7.)
_playRevealAnimation();
else
setState(() {
_swipeUpDy = dy * - 1;
});
}
Copy the code
In the display animation, use CustomPainter to draw the curve background and the amoeba background. During animation, the height of the curved background and the control points in between were interpolated to 75% of the screen height. Similarly, an amoeba drawn with a Bezier curve is vertically translated.
//Update scene state to "reveal" and start corresponding animation
//This method is called when drag excced our defined threshold
void _playRevealAnimation() {
setState(() {
currentScreenState = CURRENT_SCREEN_STATE.REVEALING_ANIMATING_STATE;
_revealAnimationController.forward();
_amoebaAnimationController.forward();
});
}
//Animation controller for expanding the curve animation
_revealAnimationController =
AnimationController(duration: Duration(milliseconds: 500), vsync: this)
..addStatusListener((status) {
if (status == AnimationStatus.completed)
setState(() {
currentScreenState = CURRENT_SCREEN_STATE.POST_REVEAL_STATE;
_postRevealAnimationController.forward();
});
});
//Animation to translate the brand label
_titleBaseLinePosTranslateAnim = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0,
widget.height -
_initialCurveHeight -
widget.height * 2. -
arcHeight,
0,
_initialCurveHeight),
end: RelativeRect.fromLTRB(
0,
widget.height - _finalCurveHeight - 20 - arcHeight,
0,
_finalCurveHeight))
.animate(CurvedAnimation(
parent: _revealAnimationController, curve: Curves.easeOutBack));
//Animation to translate side icons
_sideIconsTranslateAnim = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0,
widget.height -
_initialCurveHeight -
widget.height * 25. -
arcHeight,
0,
_initialCurveHeight),
end: RelativeRect.fromLTRB(
0,
widget.height -
_finalCurveHeight -
widget.height * 25. -
arcHeight,
0,
_finalCurveHeight))
.animate(CurvedAnimation(
parent: _revealAnimationController, curve: Curves.easeInOutBack));
//Tween for animating height of the curve during reveal process
_swipeArcAnimation =
Tween<double>(begin: _initialCurveHeight, end: _finalCurveHeight)
.animate(CurvedAnimation(
parent: _revealAnimationController, curve: Curves.easeInCubic));
//Animation for the mid control point of cubic bezier curve to show acceleration effect in response to user drag.
_swipeArchHeightAnimation = TweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0, end: 200),
weight: 50.0,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 200, end: 0),
weight: 50.0,
),
],
).animate(CurvedAnimation(
parent: _revealAnimationController, curve: Curves.easeInCubic));
//Animation Controller for amoeba background
_amoebaAnimationController =
AnimationController(duration: Duration(milliseconds: 350), vsync: this);
_amoebaOffsetAnimation =
Tween<Offset>(begin: Offset(0.0), end: Offset(- 20.- 70.)).animate(
CurvedAnimation(
parent: _amoebaAnimationController,
curve: Curves.easeInOutBack));
Copy the code
Once the animation is complete, scene 2 is set up. In this scenario, brand titles are replaced by ICONS and label indicators are displayed from the left side of the screen.
//Animation controller for showing animation after reveal
_postRevealAnimationController =
AnimationController(duration: Duration(milliseconds: 600), vsync: this);
//Scale animation for showing center logo after reveal is completed
_centerIconScale = Tween<double>(begin: 0, end: . 5).animate(CurvedAnimation(
parent: _postRevealAnimationController,
curve: Curves.fastOutSlowIn,
));
//_centerIconScale animation used by FAB in the middle
Positioned.fromRelativeRect(
rect: _titleBaseLinePosTranslateAnim.value.shift(Offset(0.18)),
child: ScaleTransition(
scale: _centerIconScale,
child: FloatingActionButton(
backgroundColor: Colors.white,
elevation: 5,
onPressed: null,
child: Icon(Icons.monetization_on_outlined,
size: 100,
color: isLeftTabSelected
? Colors.deepPurple
: Colors.pinkAccent))),
),
//Tab selection is done by "CurvePageSwitchIndicator" widget
Positioned(
top: 0,
bottom: _titleBaseLinePosTranslateAnim.value.bottom,
left: 0,
right: 0,
child: CurvePageSwitchIndicator(widget.height, widget.width, arcHeight, 3.true, _onLeftTabSelectd, _onRightTabSelectd),
);
//The build method of CurvePageSwitchIndicator consisting of "CurvePageSwitcher" CustomPainter to paint tab selection arc
//and Gesture detectors stacked on top to intercept left and right tap event.
///When the reveal scene is completed, left tab is selected and the tab selection fly
//towards from the left side of the screen
@override
Widget build(BuildContext context) {
return Stack(children: [
Transform(
transform: Matrix4.identity()
..setEntry(0.3, translationDxAnim.value) .. setEntry(1.3, translationDyAnim.value) .. rotateZ(rotationAnim.value *3.14 / 180),
alignment: Alignment.bottomLeft,
child: Container(
height: double.infinity,
width: double.infinity, child: CustomPaint( painter: CurvePageSwitcher( widget.arcHeight, widget.arcBottomOffset, showLeftAsFirstPage, pageTabAnimationController!) , ), )), Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( child: Stack(children: [ Positioned( left:0,
right: 20,
bottom: 0,
top: 90,
child: Transform.rotate(
angle: - 13 * 3.14 / 180,
child: Align(
alignment: Alignment.center,
child: Text(
"Login",
style: TextStyle(
color: showLeftAsFirstPage
? Colors.white
: Colors.white60,
fontSize: 22,
fontWeight: FontWeight.w800),
)))),
GestureDetector(onTap: _handleLeftTab,
)
])),
Expanded(
child: Stack(children: [
Positioned(
left: 20,
right: 0,
bottom: 0,
top: 90,
child: Transform.rotate(
angle: 13 * 3.14 / 180,
child: Align(
alignment: Alignment.center,
child: Text("Signup", style: TextStyle( color: ! showLeftAsFirstPage ? Colors.white : Colors.white60, fontSize:22,
fontWeight: FontWeight.w800))))),
GestureDetector(onTap: _handleRightTab,
)
])),
],
),
]);
}
Copy the code
The TAB indicator is also drawn using bezier curves and positioned above the curved background of Scene 1, but in a separate CustomPainter. To achieve the effect of TAB selection, clipping paths are used when drawing TAB selection curves.
//The paint method of "CurvePageSwitcher" to draw tab selection arc
void _drawSwipeAbleArc(Canvas canvas, Size size) {
Path path = Path();
path.moveTo(2 -, size.height - archBottomOffset);
path.cubicTo(
2 -,
size.height - archBottomOffset,
size.width / 2,
size.height - arcHeight - archBottomOffset,
size.width + 2,
size.height - archBottomOffset);
path.moveTo(size.width + 2, size.height - archBottomOffset);
path.close();
double left, right;
if (showLeftAsFirstPage) {
left = size.width / 2 - size.width / 2 * animation.value;
right = size.width / 2;
swipeArcPaint.color = Colors.green;
} else {
left = size.width / 2;
right = size.width * animation.value;
swipeArcPaint.color = Colors.deepPurple;
}
canvas.clipRect(Rect.fromLTRB(left, 0, right, size.height));
canvas.drawPath(path, swipeArcPaint);
}
Copy the code
In addition, the two containers are placed on top of each other in their respective label colors. According to the selected TAB, retain the corresponding container, translate the other container to the opposite end of the X axis, and thus discard the other container.
///The background for selected tab. On the basis of tab selected, the foreground container is translated away,
///revealing the underlying background container. If the screen state is just set to reveal, then in the
///initial state no foreground container is added which is signified by _tabSelectionAnimation set to null.
///_tabSelectionAnimation is only set when either of the tab is pressed.
List<Widget> _getBgWidgets() {
List<Widget> widgets = [];
Color foreGroundColor;
Color backgroundColor;
if (isLeftTabSelected) {
foreGroundColor = Colors.deepPurple;
backgroundColor = Colors.pink;
} else {
foreGroundColor = Colors.pink;
backgroundColor = Colors.deepPurple;
}
widgets.add(Positioned.fill(child: Container(color: foreGroundColor)));
if(_tabSelectionAnimation ! =null) widgets.add(PositionedTransition( rect: _tabSelectionAnimation! , child: Container( decoration: BoxDecoration( color: backgroundColor ), ))); widgets.add(Container( height:double.infinity,
width: double.infinity,
child: CustomPaint(
painter: AmoebaBg(_amoebaOffsetAnimation),
),
));
return widgets;
}
Copy the code
Since I couldn’t get exact images and resources, I used the closest one I could find online.
So in general, what we get is the following.
The elder brother of the © cat
ducafecat.tech/
github.com/ducafecat
The issue of
Open source
GetX Quick Start
Github.com/ducafecat/g…
News client
Github.com/ducafecat/f…
Strapi manual translation
getstrapi.cn
Wechat discussion group Ducafecat
A series of collections
The translation
Ducafecat. Tech/categories /…
The open source project
Ducafecat. Tech/categories /…
Dart programming language basics
Space.bilibili.com/404904528/c…
Start Flutter with zero basics
Space.bilibili.com/404904528/c…
Flutter combat news client from scratch
Space.bilibili.com/404904528/c…
Flutter component development
Space.bilibili.com/404904528/c…
Flutter Bloc
Space.bilibili.com/404904528/c…
Flutter Getx4
Space.bilibili.com/404904528/c…
Docker Yapi
Space.bilibili.com/404904528/c…