
Chào các đệ tử của anh Creyt! Hôm nay, chúng ta sẽ "bóc tách" một khái niệm mà nhiều bạn trẻ hay lúng túng khi muốn tạo ra những animation "xịn xò" hơn là chỉ đi từ A đến B một cách nhàm chán. Đó chính là TweenSequence – và cái "ruột gan" của nó là TweenSequenceState. Nghe tên có vẻ hàn lâm, nhưng thật ra nó là một ông DJ cực chất, giúp bạn phối nhạc cho các hiệu ứng chuyển động trong app Flutter của mình đấy!
1. TweenSequence là gì và để làm gì? (Và TweenSequenceState là "trái tim" của nó)
Tưởng tượng thế này: Bạn muốn làm một cái animation. Nếu chỉ là đổi màu từ đỏ sang xanh, hay phóng to từ 0 lên 100, thì một cái Tween đơn giản là đủ. Nó như một chuyến bay thẳng từ Hà Nội vào Sài Gòn vậy. Easy game.
Nhưng đời đâu phải lúc nào cũng đơn giản đúng không? Giờ bạn muốn cái app của mình nó 'bay' thế này: Đầu tiên, cái nút nó rung nhẹ một tí, xong nhảy lên một đoạn, rồi phát sáng rực rỡ, xong mới hạ cánh xuống vị trí cuối cùng. Đó, một loạt các hành động nối tiếp nhau, mỗi hành động lại có một 'cá tính' riêng (tốc độ, kiểu chuyển động, thời gian). Nếu dùng Tween đơn lẻ, bạn sẽ phải ngồi canh thời gian, tính toán tùm lum, đau đầu lắm.
Đây chính là lúc TweenSequence xuất hiện như một "vị cứu tinh". Nó cho phép bạn xâu chuỗi nhiều Tween nhỏ lại với nhau thành một chuỗi animation liền mạch. Mỗi Tween nhỏ trong chuỗi được gọi là một TweenSequenceItem. Anh em cứ hình dung TweenSequence như một đạo diễn tài ba, sắp xếp từng cảnh quay (từng TweenSequenceItem) một cách hợp lý để tạo nên một bộ phim (animation) hoàn chỉnh.
Còn TweenSequenceState? À, đó chính là "bộ não" hay "trái tim" của ông đạo diễn TweenSequence này đấy. Nó là cái thứ đứng sau cánh gà, âm thầm quản lý từng giai đoạn của chuỗi, tính toán xem ở thời điểm hiện tại thì cái Tween nào đang chạy, nó chạy được bao nhiêu phần trăm rồi, và trả về giá trị tương ứng. Chúng ta thường không tương tác trực tiếp với TweenSequenceState mà là thông qua TweenSequence thôi, nhưng biết nó tồn tại và làm nhiệm vụ gì thì sẽ giúp bạn hiểu sâu hơn về cách animation của Flutter hoạt động.
2. Code Ví Dụ Minh Hoạ: "Nút Bấm Nhảy Múa"
Để dễ hình dung, anh em mình cùng làm một cái nút bấm nó 'nhảy múa' theo nhiều giai đoạn nhé. Nút này sẽ:
- Nhỏ lại một chút.
- Phóng to ra một chút.
- Đổi màu từ xanh sang vàng.
- Cuối cùng là trở về trạng thái ban đầu.
Chúng ta sẽ dùng TweenSequence để điều khiển cả kích thước và màu sắc.
import 'package:flutter/material.dart';
class TweenSequenceDemo extends StatefulWidget {
const TweenSequenceDemo({super.key});
@override
State<TweenSequenceDemo> createState() => _TweenSequenceDemoState();
}
class _TweenSequenceDemoState extends State<TweenSequenceDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _sizeAnimation;
late Animation<Color?> _colorAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 4), // Tổng thời gian animation
);
// Định nghĩa sequence cho kích thước
_sizeAnimation = TweenSequence<double>([
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.8), // Giai đoạn 1: Nhỏ lại
weight: 20.0, // Chiếm 20% tổng thời gian
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.8, end: 1.2), // Giai đoạn 2: Phóng to
weight: 30.0, // Chiếm 30% tổng thời gian
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.2, end: 1.0), // Giai đoạn 3: Về kích thước ban đầu
weight: 50.0, // Chiếm 50% tổng thời gian
),
]).animate(_controller);
// Định nghĩa sequence cho màu sắc
_colorAnimation = TweenSequence<Color?>([
TweenSequenceItem<Color?>(
tween: ColorTween(begin: Colors.blue, end: Colors.red), // Giai đoạn 1: Xanh -> Đỏ
weight: 50.0, // Chiếm 50% tổng thời gian
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: Colors.red, end: Colors.green), // Giai đoạn 2: Đỏ -> Xanh lá
weight: 50.0, // Chiếm 50% tổng thời gian
),
]).animate(_controller);
// Lặp lại animation sau khi hoàn thành
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('TweenSequence Demo')),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _sizeAnimation.value,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _colorAnimation.value,
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Text(
'Tap Me!',
style: TextStyle(
color: Colors.white,
fontSize: 16 * _sizeAnimation.value, // Font size cũng thay đổi theo scale
),
),
),
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_controller.isAnimating) {
_controller.stop();
} else {
_controller.repeat(reverse: true);
}
},
child: Icon(_controller.isAnimating ? Icons.pause : Icons.play_arrow),
),
);
}
}
void main() {
runApp(const MaterialApp(home: TweenSequenceDemo()));
}
Trong ví dụ trên:
- Chúng ta tạo ra hai chuỗi
TweenSequenceđộc lập: một cho_sizeAnimation(kích thước) và một cho_colorAnimation(màu sắc). - Mỗi
TweenSequenceItemcó mộttweenriêng và mộtweight.weightở đây không phải là trọng lượng, mà là "tỷ lệ thời gian" màtweenđó chiếm trong tổng thời gian củaTweenSequence. Tổngweightcủa tất cảTweenSequenceItemtrong mộtTweenSequencesẽ được dùng để tính toán tỷ lệ. Ví dụ, nếu tổngweightlà 100, thìweight: 20có nghĩa làtweenđó chạy trong 20% tổng thời gian của_controller.

3. Mẹo (Best Practices) từ ông Creyt để "quẩy" với TweenSequence
weightlà chìa khóa, không phải thời gian tuyệt đối: Nhớ nhé,weightlà tỷ lệ, không phải số giây. Tổngweightcủa tất cảTweenSequenceItemtrong mộtTweenSequencesẽ được chuẩn hóa. Ví dụ, nếu bạn có 3 item vớiweightlà 1, 2, 1, thì tổng là 4. Item 1 sẽ chiếm 1/4 thời gian, item 2 chiếm 2/4, item 3 chiếm 1/4. Đừng cố gắng làm tổngweightluôn bằng 100 nếu không cần thiết, cứ để nó tự tính toán.- Kết hợp
Curvecho mỗiTweenSequenceItem: MỗiTweentrongTweenSequenceItemcó thể cóCurveriêng của nó (ví dụ:Curve.easeIn,Curve.bounceOut). Điều này giúp bạn tạo ra các hiệu ứng chuyển động rất "mượt mà" và "có hồn" hơn. Đừng chỉ dùngLinear, hãy thử nghiệm! - Dùng
AnimatedBuilder: Như ví dụ trên,AnimatedBuilderlà cách "chuẩn bài" để lắng nghe sự thay đổi củaAnimationControllervà rebuild widget một cách hiệu quả, tránhsetStatetoàn bộ widget tree không cần thiết. - Suy nghĩ "theo chuỗi": Khi nào bạn thấy cần một hiệu ứng mà nó diễn ra theo từng bước, từng giai đoạn rõ ràng, thì
TweenSequencechính là ứng cử viên sáng giá. Còn nếu chỉ là một chuyển động đơn giản từ A đến B, thìTweenthường (không có Sequence) sẽ nhẹ nhàng và dễ quản lý hơn.
4. Ứng Dụng Thực Tế: Ai đã dùng TweenSequence rồi?
Nói về ứng dụng thực tế thì nhiều vô kể, anh em cứ để ý mấy cái app hay game đẹp đẽ mà xem:
- Màn hình Intro/Loading: Khi app khởi động, các icon có thể từ từ hiện ra, phóng to, đổi màu theo một trình tự nhất định.
- Onboarding Flow: Mấy cái màn hình giới thiệu tính năng lúc mới cài app ấy. Các phần tử UI có thể di chuyển, biến đổi theo từng bước, dẫn dắt người dùng.
- Game UI/Animations: Trong game, khi nhân vật tung chiêu, nhận sát thương, hay đơn giản là đứng yên (idle animation), các hiệu ứng có thể là một chuỗi các chuyển động nhỏ nối tiếp nhau.
- Phản hồi người dùng (User Feedback): Khi nhấn một nút, nút đó không chỉ đổi màu mà có thể "nhảy" lên một tí, rung nhẹ, hoặc phát sáng theo một chuỗi. Giúp người dùng cảm thấy "có tương tác" hơn.
- TikTok-style Transitions: Mấy hiệu ứng chuyển cảnh "mượt mà" và phức tạp giữa các video, các story trên Instagram/Facebook cũng có thể được xây dựng bằng cách xâu chuỗi nhiều animation nhỏ.
5. Thử Nghiệm và Nên Dùng Cho Case Nào? (Kinh nghiệm xương máu của Creyt)
Anh Creyt đã từng "vật lộn" với việc tạo ra các animation phức tạp mà không dùng TweenSequence rồi. Hồi đó, cứ phải ngồi tính toán interval thủ công, dùng CurvedAnimation với các begin và end khác nhau, xong rồi addListener tùm lum để cập nhật trạng thái. Kết quả là code dài dòng, khó đọc, và dễ phát sinh bug khi cần chỉnh sửa thời gian.
Từ khi biết đến TweenSequence, mọi thứ trở nên "dễ thở" hơn hẳn. Anh em nên dùng TweenSequence khi:
- Animation có nhiều "chặng" rõ ràng: Mỗi chặng có một hành vi riêng (tăng tốc, giảm tốc, đổi màu, di chuyển...).
- Cần sự phối hợp nhịp nhàng giữa các hiệu ứng: Ví dụ, một vật thể di chuyển xong thì mới đổi màu, đổi màu xong mới xoay.
TweenSequencegiúp bạn định nghĩa trình tự này một cách trực quan. - Muốn code "sạch" và dễ bảo trì hơn: Thay vì nhiều
Tweenđộc lập vàCurvedAnimationlồng ghép,TweenSequencegom chúng lại thành một khối logic.
Tuy nhiên, đừng lạm dụng nó nhé! Nếu animation của bạn chỉ là một chuyển động đơn giản, hoặc các hiệu ứng diễn ra song song mà không cần trình tự, thì việc dùng TweenSequence có thể hơi "quá đà". Đôi khi, một Tween kết hợp với CurvedAnimation đã là đủ rồi. Quan trọng là chọn đúng công cụ cho đúng việc, giống như việc bạn chọn đúng loại cà phê cho buổi sáng vậy: đôi khi chỉ cần đen đá, đôi khi lại cần một ly Latte cầu kỳ.
Vậy đó, TweenSequence không chỉ là một công cụ, nó là một tư duy giúp bạn "điều phối" các animation phức tạp một cách thanh lịch. Hãy thực hành và biến những ý tưởng animation "điên rồ" nhất của bạn thành hiện thực nhé các đệ!
Thuộc Series: Flutter
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!