Skip to main content

Animation và Custom Painting Trong Flutter

· 3 min read

Animation Cơ bản

AnimationController

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

Tween Animation

final animation = Tween<double>(
begin: 0,
end: 300,
).animate(_controller);

Các Loại Animation

Implicit Animations

AnimatedContainer(
duration: Duration(milliseconds: 500),
width: _isExpanded ? 300.0 : 100.0,
height: _isExpanded ? 300.0 : 100.0,
color: _isExpanded ? Colors.blue : Colors.red,
curve: Curves.fastOutSlowIn,
)

Hero Animation

Hero(
tag: 'imageHero',
child: Image.network('url_to_image'),
)

Staggered Animations

class StaggeredAnimation extends StatelessWidget {
final Animation<double> controller;
late final Animation<double> opacity;
late final Animation<double> width;
late final Animation<double> height;

StaggeredAnimation({required this.controller}) {
opacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(0.0, 0.100, curve: Curves.ease),
),
);
}
}

Custom Painting

CustomPaint và CustomPainter

class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 4
..style = PaintingStyle.stroke;

canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
100,
paint,
);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Vẽ Đường Cong

void drawCurve(Canvas canvas, Size size) {
var path = Path();
path.moveTo(0, size.height / 2);
path.quadraticBezierTo(
size.width / 2,
0,
size.width,
size.height / 2,
);
canvas.drawPath(path, paint);
}

Hiệu Ứng Nâng Cao

Particle System

class Particle {
Offset position;
double speed;
double theta;
Color color;

void update() {
final dx = speed * cos(theta);
final dy = speed * sin(theta);
position += Offset(dx, dy);
}

void draw(Canvas canvas) {
final paint = Paint()..color = color;
canvas.drawCircle(position, 2, paint);
}
}

Shader Effects

final shader = LinearGradient(
colors: [Colors.blue, Colors.red],
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));

final paint = Paint()..shader = shader;

Performance Optimization

Repaint Boundary

RepaintBoundary(
child: CustomPaint(
painter: MyPainter(),
),
)

Caching Complex Paintings

class CachedPainter extends CustomPainter {
ui.Picture? _cachedPicture;

void _createCachedPicture(Size size) {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
// Draw complex stuff
_cachedPicture = recorder.endRecording();
}
}

Best Practices

Animation

  • Sử dụng vsync để tránh memory leak
  • Dispose AnimationController khi widget bị dispose
  • Sử dụng Implicit Animation khi có thể
  • Tránh animation quá phức tạp trên mobile

Custom Painting

  • Sử dụng RepaintBoundary để tối ưu hiệu năng
  • Cache các painting phức tạp
  • Tránh vẽ lại không cần thiết

Tài Liệu Tham Khảo