chore: bring flutter client, docs, deploy and tools into monorepo
This commit is contained in:
+345
@@ -0,0 +1,345 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Explicit animation example using AnimationController
|
||||
///
|
||||
/// This example demonstrates:
|
||||
/// - AnimationController setup and lifecycle
|
||||
/// - Tween interpolation
|
||||
/// - AnimatedWidget pattern for reusable animations
|
||||
/// - AnimatedBuilder pattern for complex widgets
|
||||
/// - Animation status monitoring
|
||||
|
||||
void main() => runApp(const ExplicitAnimationApp());
|
||||
|
||||
class ExplicitAnimationApp extends StatelessWidget {
|
||||
const ExplicitAnimationApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Explicit Animation',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
home: const LogoAnimationDemo(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic explicit animation with addListener and setState
|
||||
class BasicExplicitAnimation extends StatefulWidget {
|
||||
const BasicExplicitAnimation({super.key});
|
||||
|
||||
@override
|
||||
State<BasicExplicitAnimation> createState() => _BasicExplicitAnimationState();
|
||||
}
|
||||
|
||||
class _BasicExplicitAnimationState extends State<BasicExplicitAnimation>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = Tween<double>(begin: 0, end: 300).animate(_controller)
|
||||
..addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Basic Explicit')),
|
||||
body: Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
height: _animation.value,
|
||||
width: _animation.value,
|
||||
child: const FlutterLogo(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// AnimatedWidget pattern - best for reusable animated widgets
|
||||
class AnimatedLogo extends AnimatedWidget {
|
||||
const AnimatedLogo({super.key, required Animation<double> animation})
|
||||
: super(listenable: animation);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final animation = listenable as Animation<double>;
|
||||
return Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
height: animation.value,
|
||||
width: animation.value,
|
||||
child: const FlutterLogo(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedWidgetDemo extends StatefulWidget {
|
||||
const AnimatedWidgetDemo({super.key});
|
||||
|
||||
@override
|
||||
State<AnimatedWidgetDemo> createState() => _AnimatedWidgetDemoState();
|
||||
}
|
||||
|
||||
class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = Tween<double>(begin: 0, end: 300).animate(_controller);
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Animated Widget')),
|
||||
body: AnimatedLogo(animation: _animation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// AnimatedBuilder pattern - best for complex widgets
|
||||
class LogoWidget extends StatelessWidget {
|
||||
const LogoWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: const FlutterLogo(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GrowTransition extends StatelessWidget {
|
||||
const GrowTransition({
|
||||
required this.child,
|
||||
required this.animation,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Animation<double> animation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: animation.value,
|
||||
width: animation.value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedBuilderDemo extends StatefulWidget {
|
||||
const AnimatedBuilderDemo({super.key});
|
||||
|
||||
@override
|
||||
State<AnimatedBuilderDemo> createState() => _AnimatedBuilderDemoState();
|
||||
}
|
||||
|
||||
class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = Tween<double>(begin: 0, end: 300).animate(_controller);
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Animated Builder')),
|
||||
body: GrowTransition(animation: _animation, child: const LogoWidget()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple simultaneous animations with status monitoring
|
||||
class MultiPropertyAnimation extends AnimatedWidget {
|
||||
const MultiPropertyAnimation({
|
||||
super.key,
|
||||
required Animation<double> animation,
|
||||
}) : super(listenable: animation);
|
||||
|
||||
static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
|
||||
static final _sizeTween = Tween<double>(begin: 0, end: 300);
|
||||
static final _colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final animation = listenable as Animation<double>;
|
||||
return Center(
|
||||
child: Opacity(
|
||||
opacity: _opacityTween.evaluate(animation),
|
||||
child: Container(
|
||||
height: _sizeTween.evaluate(animation),
|
||||
width: _sizeTween.evaluate(animation),
|
||||
decoration: BoxDecoration(
|
||||
color: _colorTween.evaluate(animation),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const FlutterLogo(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MultiPropertyDemo extends StatefulWidget {
|
||||
const MultiPropertyDemo({super.key});
|
||||
|
||||
@override
|
||||
State<MultiPropertyDemo> createState() => _MultiPropertyDemoState();
|
||||
}
|
||||
|
||||
class _MultiPropertyDemoState extends State<MultiPropertyDemo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(seconds: 2),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeIn)
|
||||
..addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_controller.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
_controller.forward();
|
||||
}
|
||||
});
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Multi Property')),
|
||||
body: MultiPropertyAnimation(animation: _animation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Main demo widget showing all examples
|
||||
class LogoAnimationDemo extends StatelessWidget {
|
||||
const LogoAnimationDemo({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Explicit Animations')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const BasicExplicitAnimation(),
|
||||
),
|
||||
),
|
||||
child: const Text('Basic Explicit'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const AnimatedWidgetDemo(),
|
||||
),
|
||||
),
|
||||
child: const Text('Animated Widget'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const AnimatedBuilderDemo(),
|
||||
),
|
||||
),
|
||||
child: const Text('Animated Builder'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const MultiPropertyDemo(),
|
||||
),
|
||||
),
|
||||
child: const Text('Multi Property'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Hero animation example for shared element transitions
|
||||
///
|
||||
/// This example demonstrates:
|
||||
/// - Basic hero animation with matching tags
|
||||
/// - Custom PhotoHero widget
|
||||
/// - Navigation between screens with hero transition
|
||||
/// - MaterialRectCenterArcTween for aspect ratio preservation
|
||||
|
||||
void main() => runApp(const HeroAnimationApp());
|
||||
|
||||
class HeroAnimationApp extends StatelessWidget {
|
||||
const HeroAnimationApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Hero Animation',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
home: const GalleryScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom hero widget for images
|
||||
class PhotoHero extends StatelessWidget {
|
||||
const PhotoHero({
|
||||
super.key,
|
||||
required this.photo,
|
||||
this.onTap,
|
||||
required this.width,
|
||||
});
|
||||
|
||||
final String photo;
|
||||
final VoidCallback? onTap;
|
||||
final double width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: Hero(
|
||||
tag: photo,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Image.asset(
|
||||
photo,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: width,
|
||||
color: Colors.grey,
|
||||
child: const Icon(Icons.broken_image),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gallery screen showing multiple hero images
|
||||
class GalleryScreen extends StatelessWidget {
|
||||
const GalleryScreen({super.key});
|
||||
|
||||
static const List<String> _photos = [
|
||||
'images/photo1.png',
|
||||
'images/photo2.png',
|
||||
'images/photo3.png',
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Hero Gallery')),
|
||||
body: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
),
|
||||
itemCount: _photos.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PhotoHero(
|
||||
photo: _photos[index],
|
||||
width: 100,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => DetailScreen(photo: _photos[index]),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Detail screen showing hero image in fullscreen
|
||||
class DetailScreen extends StatelessWidget {
|
||||
const DetailScreen({super.key, required this.photo});
|
||||
|
||||
final String photo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Detail')),
|
||||
body: Center(
|
||||
child: PhotoHero(
|
||||
photo: photo,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Example with circular hero (using placeholder instead of asset)
|
||||
class CircularHeroExample extends StatefulWidget {
|
||||
const CircularHeroExample({super.key});
|
||||
|
||||
@override
|
||||
State<CircularHeroExample> createState() => _CircularHeroExampleState();
|
||||
}
|
||||
|
||||
class _CircularHeroExampleState extends State<CircularHeroExample> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Circular Hero')),
|
||||
body: ListView(
|
||||
children: List.generate(3, (index) {
|
||||
final tag = 'circle-$index';
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Hero(
|
||||
tag: tag,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) =>
|
||||
CircularDetailScreen(tag: tag, index: index),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CircularDetailScreen extends StatelessWidget {
|
||||
const CircularDetailScreen({
|
||||
super.key,
|
||||
required this.tag,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
final String tag;
|
||||
final int index;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Circular Detail')),
|
||||
body: Center(
|
||||
child: Hero(
|
||||
tag: tag,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: 0.8),
|
||||
borderRadius: BorderRadius.circular(150),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Image $index',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 24),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Implicit animation example showing basic fade animation
|
||||
///
|
||||
/// This example demonstrates:
|
||||
/// - AnimatedOpacity for fade in/out
|
||||
/// - State-driven animation triggers
|
||||
/// - Simple, declarative animation code
|
||||
|
||||
void main() => runApp(const ImplicitAnimationApp());
|
||||
|
||||
class ImplicitAnimationApp extends StatelessWidget {
|
||||
const ImplicitAnimationApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Implicit Animation',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
home: const FadeExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FadeExample extends StatefulWidget {
|
||||
const FadeExample({super.key});
|
||||
|
||||
@override
|
||||
State<FadeExample> createState() => _FadeExampleState();
|
||||
}
|
||||
|
||||
class _FadeExampleState extends State<FadeExample> {
|
||||
bool _visible = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Implicit Fade')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
opacity: _visible ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
child: const FlutterLogo(size: 100),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => setState(() => _visible = !_visible),
|
||||
child: Text(_visible ? 'Fade Out' : 'Fade In'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// AnimatedContainer example - animating multiple properties
|
||||
class AnimatedContainerExample extends StatefulWidget {
|
||||
const AnimatedContainerExample({super.key});
|
||||
|
||||
@override
|
||||
State<AnimatedContainerExample> createState() =>
|
||||
_AnimatedContainerExampleState();
|
||||
}
|
||||
|
||||
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
|
||||
bool _expanded = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Animated Container')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
width: _expanded ? 200 : 100,
|
||||
height: _expanded ? 200 : 100,
|
||||
decoration: BoxDecoration(
|
||||
color: _expanded ? Colors.blue : Colors.red,
|
||||
borderRadius: BorderRadius.circular(_expanded ? 20 : 8),
|
||||
),
|
||||
child: const FlutterLogo(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => setState(() => _expanded = !_expanded),
|
||||
child: Text(_expanded ? 'Shrink' : 'Expand'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// TweenAnimationBuilder example - custom tween without boilerplate
|
||||
class TweenAnimationBuilderExample extends StatefulWidget {
|
||||
const TweenAnimationBuilderExample({super.key});
|
||||
|
||||
@override
|
||||
State<TweenAnimationBuilderExample> createState() =>
|
||||
_TweenAnimationBuilderExampleState();
|
||||
}
|
||||
|
||||
class _TweenAnimationBuilderExampleState
|
||||
extends State<TweenAnimationBuilderExample> {
|
||||
bool _animated = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Tween Animation Builder')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(begin: 0, end: 1),
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
builder: (context, value, child) {
|
||||
return Opacity(
|
||||
opacity: value,
|
||||
child: Transform.scale(scale: value, child: child),
|
||||
);
|
||||
},
|
||||
child: const FlutterLogo(size: 100),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => setState(() => _animated = !_animated),
|
||||
child: const Text('Animate'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Staggered animation example with multiple property animations
|
||||
///
|
||||
/// This example demonstrates:
|
||||
/// - Single AnimationController driving multiple animations
|
||||
/// - Interval-based timing for sequential/overlapping animations
|
||||
/// - Multiple tweens for different properties
|
||||
/// - Status monitoring for animation loops
|
||||
|
||||
void main() => runApp(const StaggeredAnimationApp());
|
||||
|
||||
class StaggeredAnimationApp extends StatelessWidget {
|
||||
const StaggeredAnimationApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Staggered Animation',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
home: const StaggeredDemo(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Staggered animation with fade, scale, and color changes
|
||||
class StaggeredAnimation extends StatelessWidget {
|
||||
StaggeredAnimation({super.key, required this.animation});
|
||||
|
||||
late final Animation<double> opacity = Tween<double>(begin: 0.0, end: 1.0)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.0, 0.1, curve: Curves.ease),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> width = Tween<double>(begin: 50.0, end: 150.0)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.125, 0.25, curve: Curves.ease),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> height = Tween<double>(begin: 50.0, end: 150.0)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.25, 0.375, curve: Curves.ease),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<BorderRadius?> borderRadius =
|
||||
BorderRadiusTween(
|
||||
begin: BorderRadius.circular(4),
|
||||
end: BorderRadius.circular(75),
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.375, 0.5, curve: Curves.ease),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<Color?> color =
|
||||
ColorTween(begin: Colors.red, end: Colors.orange).animate(
|
||||
CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: const Interval(0.5, 0.625, curve: Curves.ease),
|
||||
),
|
||||
);
|
||||
|
||||
final Animation<double> animation;
|
||||
|
||||
Widget _buildAnimation(BuildContext context, Widget? child) {
|
||||
return Center(
|
||||
child: Opacity(
|
||||
opacity: opacity.value,
|
||||
child: Container(
|
||||
width: width.value,
|
||||
height: height.value,
|
||||
decoration: BoxDecoration(
|
||||
color: color.value,
|
||||
border: Border.all(color: Colors.indigo[300]!, width: 3),
|
||||
borderRadius: borderRadius.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(animation: animation, builder: _buildAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
class StaggeredDemo extends StatefulWidget {
|
||||
const StaggeredDemo({super.key});
|
||||
|
||||
@override
|
||||
State<StaggeredDemo> createState() => _StaggeredDemoState();
|
||||
}
|
||||
|
||||
class _StaggeredDemoState extends State<StaggeredDemo>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 2000),
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _playAnimation() async {
|
||||
try {
|
||||
await _controller.forward().orCancel;
|
||||
await _controller.reverse().orCancel;
|
||||
} on TickerCanceled {
|
||||
// Animation was canceled
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Staggered Animation')),
|
||||
body: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _playAnimation,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 300,
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
border: Border.all(color: Colors.black.withValues(alpha: 0.5)),
|
||||
),
|
||||
child: StaggeredAnimation(animation: _controller.view),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Staggered menu animation example
|
||||
class StaggeredMenuExample extends StatefulWidget {
|
||||
const StaggeredMenuExample({super.key});
|
||||
|
||||
@override
|
||||
State<StaggeredMenuExample> createState() => _StaggeredMenuExampleState();
|
||||
}
|
||||
|
||||
class _StaggeredMenuExampleState extends State<StaggeredMenuExample>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const _menuTitles = [
|
||||
'Declarative Style',
|
||||
'Premade Widgets',
|
||||
'Stateful Hot Reload',
|
||||
'Native Performance',
|
||||
'Great Community',
|
||||
];
|
||||
|
||||
static const _initialDelayTime = Duration(milliseconds: 50);
|
||||
static const _itemSlideTime = Duration(milliseconds: 250);
|
||||
static const _staggerTime = Duration(milliseconds: 50);
|
||||
|
||||
late AnimationController _controller;
|
||||
|
||||
final _animationDuration =
|
||||
_initialDelayTime + (_staggerTime * _menuTitles.length) + _itemSlideTime;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: _animationDuration,
|
||||
vsync: this,
|
||||
);
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Staggered Menu')),
|
||||
body: ListView.builder(
|
||||
itemCount: _menuTitles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final start =
|
||||
_initialDelayTime.inMilliseconds.toDouble() +
|
||||
(_staggerTime.inMilliseconds.toDouble() * index);
|
||||
final end = start + _itemSlideTime.inMilliseconds.toDouble();
|
||||
final intervalStart =
|
||||
start / _animationDuration.inMilliseconds.toDouble();
|
||||
final intervalEnd =
|
||||
end / _animationDuration.inMilliseconds.toDouble();
|
||||
|
||||
final animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(intervalStart, intervalEnd, curve: Curves.easeOut),
|
||||
);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
);
|
||||
},
|
||||
child: ListTile(title: Text(_menuTitles[index])),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user