انیمیشنها
انیمیشنها UX را به طور قابلتوجهی بهبود میدهند. Flutter ابزارهای قدرتمندی برای انیمیشن دارد — از ساده (Implicit) تا پیچیده (Explicit، Custom).
۱۰.۱ مقدمه
انیمیشنها UX را به طور قابلتوجهی بهبود میدهند. Flutter ابزارهای قدرتمندی برای انیمیشن دارد — از ساده (Implicit) تا پیچیده (Explicit، Custom).
۱۰.۲ Implicit Animations — سادهترین
Widget هایی که با تغییر property بهصورت خودکار انیمیشن میسازند. شروع همه با Animated.
// AnimatedContainer — همه چیز را animate میکند
class AnimatedBox extends StatefulWidget {
@override
State createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 300 : 100,
height: _expanded ? 200 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(_expanded ? 50 : 8),
),
child: const Center(child: Text("کلیک کنید")),
),
);
}
}
// AnimatedOpacity
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: _visible ? 1.0 : 0.0,
child: const Text("Fade in/out"),
)
// AnimatedPositioned (در Stack)
Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
top: _moved ? 100 : 50,
left: _moved ? 200 : 50,
child: Container(width: 50, height: 50, color: Colors.blue),
),
],
)
// AnimatedSwitcher (تغییر بین widget ها)
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
"$_count",
key: ValueKey(_count), // ⭐ key لازم برای detect تغییر
style: const TextStyle(fontSize: 32),
),
)
// سایر Implicit widgets:
// AnimatedAlign, AnimatedPadding, AnimatedDefaultTextStyle,
// AnimatedCrossFade, AnimatedIcon, AnimatedSize, AnimatedRotation, AnimatedScale
TweenAnimationBuilder — سفارشیسازی
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0, end: 1),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(
scale: value,
child: child,
),
);
},
child: const Text("FadeIn + ScaleIn"),
)
// Tween در رنگ
TweenAnimationBuilder(
duration: const Duration(seconds: 2),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
builder: (context, color, _) => Container(color: color, width: 100, height: 100),
)
۱۰.۳ Explicit Animations — کنترل کامل
class FadeInWidget extends StatefulWidget {
@override
State createState() => _FadeInWidgetState();
}
class _FadeInWidgetState extends State
with SingleTickerProviderStateMixin { // ⭐ Mixin لازم
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_controller.forward(); // start
}
@override
void dispose() {
_controller.dispose(); // ⚠️ مهم
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: const Text("Fade in"),
);
}
}
کنترل دستی
_controller.forward(); // 0 → 1
_controller.reverse(); // 1 → 0
_controller.repeat(); // loop
_controller.repeat(reverse: true); // 0→1→0→1...
_controller.stop();
_controller.reset();
_controller.value = 0.5; // مستقیم
// Listen to value
_controller.addListener(() {
print(_controller.value);
});
// Listen to status
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
print("done!");
}
});
Animation Widgets
// FadeTransition
FadeTransition(opacity: animation, child: ...)
// SlideTransition
SlideTransition(
position: Tween(
begin: const Offset(-1, 0), // از سمت چپ
end: Offset.zero,
).animate(_controller),
child: ...,
)
// ScaleTransition
ScaleTransition(scale: animation, child: ...)
// RotationTransition
RotationTransition(turns: animation, child: ...)
// SizeTransition
SizeTransition(sizeFactor: animation, child: ...)
// AnimatedBuilder — برای custom
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * pi,
child: child,
);
},
child: const FlutterLogo(size: 100),
)
۱۰.۴ Curves
Curve منحنی speed انیمیشن را تعیین میکند.
Curves.linear— یکنواختCurves.easeIn— آرام شروع، سریع پایانCurves.easeOut— سریع شروع، آرام پایانCurves.easeInOut— هر دوCurves.bounceOut— جهش در پایانCurves.elasticOut— کشسانCurves.fastOutSlowIn— Material استاندارد
Custom Cubic
const Cubic(0.25, 0.1, 0.25, 1.0) // مثل CSS cubic-bezier
۱۰.۵ Hero Animation
(از فصل ۵) — انیمیشن یک widget بین دو صفحه.
// در صفحه ۱
Hero(
tag: "product-1",
child: Image.network("..."),
)
// در صفحه ۲ (همان tag)
Hero(
tag: "product-1",
child: Image.network("..."),
)
۱۰.۶ Staggered Animations
چند animation که در زمانهای مختلف شروع میشوند.
class StaggeredDemo extends StatefulWidget {
@override
State createState() => _StaggeredDemoState();
}
class _StaggeredDemoState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _opacity;
late Animation _scale;
late Animation _slide;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 0% - 33% : Opacity
_opacity = Tween(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.33, curve: Curves.easeIn),
),
);
// 33% - 66% : Scale
_scale = Tween(begin: 0.5, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.33, 0.66, curve: Curves.easeOut),
),
);
// 66% - 100% : Slide
_slide = Tween(begin: const Offset(0, 0.5), end: Offset.zero).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.66, 1.0, curve: Curves.easeOut),
),
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _opacity,
child: ScaleTransition(
scale: _scale,
child: SlideTransition(
position: _slide,
child: const Card(child: Padding(
padding: EdgeInsets.all(20),
child: Text("Staggered"),
)),
),
),
);
}
}
۱۰.۷ Lottie و Rive
Lottie — انیمیشنهای After Effects
flutter pub add lottie
import "package:lottie/lottie.dart";
// از asset
Lottie.asset("assets/animations/loading.json")
// از URL
Lottie.network("https://assets.lottiefiles.com/...")
// با controller
class LottieDemo extends StatefulWidget {
@override
State createState() => _LottieDemoState();
}
class _LottieDemoState extends State
with TickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
Widget build(BuildContext context) {
return Lottie.asset(
"assets/animations/like.json",
controller: _controller,
onLoaded: (composition) {
_controller
..duration = composition.duration
..forward();
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
// انیمیشنهای رایگان: lottiefiles.com
Rive — انیمیشنهای تعاملی
flutter pub add rive
import "package:rive/rive.dart";
RiveAnimation.asset("assets/animations/character.riv")
// با state machine
StateMachineController? _controller;
SMITrigger? _bumpTrigger;
void _onRiveInit(Artboard artboard) {
_controller = StateMachineController.fromArtboard(artboard, "State Machine 1");
artboard.addController(_controller!);
_bumpTrigger = _controller!.findInput("bump") as SMITrigger;
}
ElevatedButton(
onPressed: () => _bumpTrigger?.fire(),
child: const Text("Bump!"),
)
۱۰.۸ Page Transitions سفارشی
// با go_router
GoRoute(
path: "/profile",
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const ProfilePage(),
transitionsBuilder: (_, animation, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
);
},
)
// با Navigator
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, animation, __) => const NextPage(),
transitionsBuilder: (_, animation, __, child) {
return FadeTransition(opacity: animation, child: child);
},
transitionDuration: const Duration(milliseconds: 500),
),
)
۱۰.۹ animate_do — انیمیشن سریع
پکیج ساده با انیمیشنهای آماده شبیه Animate.css.
flutter pub add animate_do
import "package:animate_do/animate_do.dart";
FadeIn(child: Text("سلام"))
FadeInUp(child: Text("از پایین"))
SlideInLeft(child: Text("از چپ"))
ZoomIn(child: Icon(Icons.star))
Bounce(child: Image.asset("..."))
Pulse(infinite: true, child: Icon(Icons.favorite))
// با delay و duration
FadeInUp(
delay: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 800),
child: const Text("..."),
)
۱۰.۱۰ بهترین تجربیات
- Implicit برای ساده. Explicit برای کنترل.
- dispose AnimationController. همیشه.
- Curve مناسب. easeInOut برای اکثر موارد.
- Duration کوتاه. 200-500ms معمولاً کافی است.
- RepaintBoundary. برای performance.
- const widgets. در child که animate نمیشود.
- Hero با tag unique.
- Lottie برای splash screen.
- تست با Flutter Inspector. Slow Animations.
- کاربر را اذیت نکنید. انیمیشن کمکی، نه آزاردهنده.
۱۰.۱۱ خلاصه فصل
- Implicit Animations: AnimatedContainer، AnimatedOpacity
- Explicit Animations با AnimationController
- Curves برای کنترل speed
- Hero animations
- Staggered animations با Interval
- Lottie و Rive برای انیمیشن پیشرفته
- Page transitions سفارشی
- animate_do برای سرعت توسعه
در فصل بعد: قابلیتهای Platform — Camera، Notification، Permission.