Overview
AnimatedRotation is a widget that automatically animates when its rotation angle (turns) changes, providing smooth rotation transitions. This widget is inspired by Flutter’s AnimatedRotation widget.
Animated version of Transform.rotate which automatically transitions the child’s rotation over a given duration whenever the given rotation changes.
Flutter reference: https://api.flutter.dev/flutter/widgets/AnimatedRotation-class.html
When to use?
- When creating loading indicators or spinners
- When visually representing state changes of icons or buttons
- When rotating elements based on user interaction
- When creating transition animations for checkboxes, switches, etc.
- When simulating movement of mechanical components
- When implementing card flip or rotation effects
Basic usage
import { AnimatedRotation, Container, StatefulWidget } from '@flitter/core';
class RotationExample extends StatefulWidget {
createState() {
return new RotationExampleState();
}
}
class RotationExampleState extends State<RotationExample> {
turns = 0;
build() {
return Column({
children: [
ElevatedButton({
onPressed: () => {
this.setState(() => {
this.turns += 0.25; // 90-degree rotation
});
},
child: Text("Rotate"),
}),
AnimatedRotation({
turns: this.turns,
duration: 500, // 0.5 seconds
child: Container({
width: 100,
height: 100,
color: "blue",
child: Center({
child: Icon({
icon: Icons.star,
color: "white",
size: 40,
}),
}),
}),
}),
],
});
}
}
Props
turns (required)
Value: number
Specifies the amount of rotation. 1.0 represents one complete full rotation (360 degrees).
- 0.0 = No rotation
- 0.25 = 90-degree rotation
- 0.5 = 180-degree rotation
- 1.0 = 360-degree rotation (one full turn)
- 2.0 = 720-degree rotation (two full turns)
- Negative values rotate counterclockwise
duration (required)
Value: number
Specifies the animation duration in milliseconds.
alignment (optional)
Value: Alignment (default: Alignment.center)
Specifies the center point of rotation. Available values:
Alignment.center
: Rotate around centerAlignment.topLeft
: Rotate around top-left cornerAlignment.topRight
: Rotate around top-right cornerAlignment.bottomLeft
: Rotate around bottom-left cornerAlignment.bottomRight
: Rotate around bottom-right corner- Custom alignment:
Alignment.of({ x: -0.5, y: 0.5 })
curve (optional)
Value: Curve (default: Curves.linear)
Specifies the animation progression curve. Available curves:
Curves.linear
: Constant speedCurves.easeIn
: Slow startCurves.easeOut
: Slow endCurves.easeInOut
: Slow start and endCurves.circIn
: Circular acceleration startCurves.circOut
: Circular deceleration endCurves.circInOut
: Circular acceleration/decelerationCurves.backIn
: Goes back then startsCurves.backOut
: Overshoots target then returnsCurves.backInOut
: backIn + backOutCurves.anticipate
: Anticipatory motion before proceedingCurves.bounceIn
: Bounces at startCurves.bounceOut
: Bounces at endCurves.bounceInOut
: Bounces at start/end
child (optional)
Value: Widget | undefined
The child widget to be rotated.
key (optional)
Value: any
A unique identifier for the widget.
Real-world examples
Example 1: Loading spinner
import { AnimatedRotation, Container, Icon, Curves } from '@flitter/core';
class LoadingSpinner extends StatefulWidget {
createState() {
return new LoadingSpinnerState();
}
}
class LoadingSpinnerState extends State<LoadingSpinner> {
turns = 0;
isLoading = false;
async startLoading() {
this.setState(() => {
this.isLoading = true;
});
// Continuous rotation animation
const interval = setInterval(() => {
if (this.isLoading) {
this.setState(() => {
this.turns += 1; // One full rotation each time
});
} else {
clearInterval(interval);
}
}, 1000);
// Complete loading after 5 seconds
setTimeout(() => {
this.setState(() => {
this.isLoading = false;
});
clearInterval(interval);
}, 5000);
}
build() {
return Column({
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedRotation({
turns: this.turns,
duration: 1000,
curve: Curves.linear,
child: Container({
width: 60,
height: 60,
decoration: BoxDecoration({
border: Border.all({ color: "blue", width: 4 }),
borderRadius: BorderRadius.circular(30),
}),
child: CustomPaint({
painter: SpinnerPainter(),
}),
}),
}),
SizedBox({ height: 20 }),
ElevatedButton({
onPressed: this.isLoading ? null : () => this.startLoading(),
child: Text(this.isLoading ? "Loading..." : "Start Loading"),
}),
],
});
}
}
Example 2: Interactive icon button
import { AnimatedRotation, Container, GestureDetector, Icon, Curves } from '@flitter/core';
class InteractiveIconButton extends StatefulWidget {
createState() {
return new InteractiveIconButtonState();
}
}
class InteractiveIconButtonState extends State<InteractiveIconButton> {
isExpanded = false;
rotation = 0;
toggle() {
this.setState(() => {
this.isExpanded = !this.isExpanded;
this.rotation += 0.5; // 180-degree rotation
});
}
build() {
return Column({
children: [
GestureDetector({
onTap: () => this.toggle(),
child: Container({
width: 80,
height: 80,
decoration: BoxDecoration({
color: this.isExpanded ? "red" : "blue",
borderRadius: BorderRadius.circular(40),
boxShadow: [
BoxShadow({
color: "rgba(0,0,0,0.3)",
blurRadius: 8,
offset: Offset({ x: 0, y: 4 }),
}),
],
}),
child: AnimatedRotation({
turns: this.rotation,
duration: 300,
curve: Curves.easeInOut,
child: Icon({
icon: this.isExpanded ? Icons.close : Icons.add,
color: "white",
size: 32,
}),
}),
}),
}),
SizedBox({ height: 20 }),
AnimatedOpacity({
opacity: this.isExpanded ? 1.0 : 0.0,
duration: 200,
child: Column({
children: [
ListTile({
leading: Icon(Icons.photo),
title: Text("Photo"),
onTap: () => {},
}),
ListTile({
leading: Icon(Icons.video),
title: Text("Video"),
onTap: () => {},
}),
ListTile({
leading: Icon(Icons.file),
title: Text("File"),
onTap: () => {},
}),
],
}),
}),
],
});
}
}
Example 3: Mechanical clock
import { AnimatedRotation, Container, Stack, Curves } from '@flitter/core';
class MechanicalClock extends StatefulWidget {
createState() {
return new MechanicalClockState();
}
}
class MechanicalClockState extends State<MechanicalClock> {
hourRotation = 0;
minuteRotation = 0;
secondRotation = 0;
initState() {
super.initState();
this.updateTime();
// Update time every second
setInterval(() => {
this.updateTime();
}, 1000);
}
updateTime() {
const now = new Date();
const hours = now.getHours() % 12;
const minutes = now.getMinutes();
const seconds = now.getSeconds();
this.setState(() => {
this.hourRotation = (hours + minutes / 60) / 12; // One turn in 12 hours
this.minuteRotation = minutes / 60; // One turn in 60 minutes
this.secondRotation = seconds / 60; // One turn in 60 seconds
});
}
build() {
return Container({
width: 200,
height: 200,
decoration: BoxDecoration({
border: Border.all({ color: "black", width: 4 }),
borderRadius: BorderRadius.circular(100),
color: "white",
}),
child: Stack({
children: [
// Clock face
...Array.from({ length: 12 }, (_, i) => {
const angle = (i * 30) * Math.PI / 180;
const x = 80 + 70 * Math.sin(angle);
const y = 80 - 70 * Math.cos(angle);
return Positioned({
left: x,
top: y,
child: Container({
width: 20,
height: 20,
child: Center({
child: Text(
(i === 0 ? 12 : i).toString(),
{ style: TextStyle({ fontWeight: "bold", fontSize: 14 }) }
),
}),
}),
});
}),
// Hour hand
Center({
child: AnimatedRotation({
turns: this.hourRotation,
duration: 1000,
curve: Curves.easeInOut,
alignment: Alignment.bottomCenter,
child: Container({
width: 4,
height: 50,
color: "black",
child: Container({
width: 4,
height: 40,
color: "black",
}),
}),
}),
}),
// Minute hand
Center({
child: AnimatedRotation({
turns: this.minuteRotation,
duration: 1000,
curve: Curves.easeInOut,
alignment: Alignment.bottomCenter,
child: Container({
width: 3,
height: 70,
color: "darkblue",
}),
}),
}),
// Second hand
Center({
child: AnimatedRotation({
turns: this.secondRotation,
duration: 100,
curve: Curves.linear,
alignment: Alignment.bottomCenter,
child: Container({
width: 1,
height: 80,
color: "red",
}),
}),
}),
// Center dot
Center({
child: Container({
width: 8,
height: 8,
decoration: BoxDecoration({
color: "black",
borderRadius: BorderRadius.circular(4),
}),
}),
}),
],
}),
});
}
}
Example 4: Card flip animation
import { AnimatedRotation, Container, GestureDetector, Curves } from '@flitter/core';
class CardFlip extends StatefulWidget {
createState() {
return new CardFlipState();
}
}
class CardFlipState extends State<CardFlip> {
isFlipped = false;
rotation = 0;
flipCard() {
this.setState(() => {
this.isFlipped = !this.isFlipped;
this.rotation += 0.5; // 180-degree rotation
});
}
build() {
const showBack = (this.rotation % 1) > 0.25 && (this.rotation % 1) < 0.75;
return GestureDetector({
onTap: () => this.flipCard(),
child: Container({
width: 200,
height: 300,
child: AnimatedRotation({
turns: this.rotation,
duration: 600,
curve: Curves.easeInOut,
child: Container({
decoration: BoxDecoration({
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow({
color: "rgba(0,0,0,0.3)",
blurRadius: 10,
offset: Offset({ x: 0, y: 5 }),
}),
],
}),
child: showBack ?
// Card back
Container({
decoration: BoxDecoration({
gradient: LinearGradient({
colors: ["#667eea", "#764ba2"],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
}),
borderRadius: BorderRadius.circular(16),
}),
child: Center({
child: Column({
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon({
icon: Icons.star,
color: "white",
size: 64,
}),
SizedBox({ height: 16 }),
Text(
"Card Back",
{ style: TextStyle({ color: "white", fontSize: 24, fontWeight: "bold" }) }
),
],
}),
}),
}) :
// Card front
Container({
decoration: BoxDecoration({
gradient: LinearGradient({
colors: ["#f093fb", "#f5576c"],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
}),
borderRadius: BorderRadius.circular(16),
}),
child: Center({
child: Column({
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon({
icon: Icons.favorite,
color: "white",
size: 64,
}),
SizedBox({ height: 16 }),
Text(
"Card Front",
{ style: TextStyle({ color: "white", fontSize: 24, fontWeight: "bold" }) }
),
SizedBox({ height: 8 }),
Text(
"Tap to flip",
{ style: TextStyle({ color: "white", fontSize: 14, opacity: 0.8 }) }
),
],
}),
}),
})
),
}),
}),
});
}
}
Notes
- turns value meaning: 1.0 represents one complete full rotation (360 degrees)
- Continuous rotation: Continuously increasing the turns value creates ongoing rotation animation
- Direction control: Negative values rotate counterclockwise
- Performance considerations: Applying rotation animation to many widgets simultaneously may impact performance
- Alignment importance: The rotation center point significantly affects the visual effect
Related widgets
- Transform.rotate: Basic widget for applying rotation without animation
- AnimatedContainer: General-purpose widget for animating multiple transformations simultaneously
- RotationTransition: Rotation animation using explicit Animation controllers
- AnimatedScale: Widget for animating size changes
- Transform: Basic widget for applying various transformations (rotation, scale, position)