
Chào mấy đứa, hôm nay anh Creyt sẽ cùng tụi em 'vibe check' một khái niệm nghe hơi hàn lâm nhưng lại cực kỳ 'main character energy' trong Flutter: TickerMode. Nghe tên thì có vẻ phức tạp, nhưng thực ra nó như một cái công tắc đa năng, giúp tụi em 'flex' hiệu suất app một cách gọn gàng, không cần phải 'simp' theo kiểu tối ưu từng chút một đâu!
1. TickerMode là gì và để làm gì? (aka 'Cái công tắc thần thánh' đó)
Trong Flutter, mọi animation đều cần một thứ gọi là Ticker. Tưởng tượng Ticker như một cái đồng hồ bấm giờ siêu tốc, mỗi khi màn hình của điện thoại refresh (khoảng 60 lần/giây), nó sẽ 'tick' một cái, báo hiệu cho animation biết là 'đã đến lúc di chuyển thêm một tí rồi đó!'. Các AnimationController mà tụi em hay dùng để tạo hiệu ứng xoay, mờ dần, hay di chuyển đều cần Ticker để hoạt động.
Nhưng mà nè, có bao giờ tụi em thấy app mình tự nhiên hơi lag lag, hay pin tụt nhanh một cách 'low-key' không? Nhiều khi là do mấy cái animation vô tư chạy 'auto-pilot' ngay cả khi chẳng ai nhìn thấy nó! Ví dụ, tụi em có một PageView với 5 tab, mỗi tab có một cái animation nhỏ xinh. Khi tụi em đang ở tab 1, thì 4 cái animation ở tab 2, 3, 4, 5 kia nó vẫn cứ chạy âm thầm trong nền, đúng là 'simp' tài nguyên máy quá đi chứ!
Đó chính là lúc TickerMode xuất hiện như một vị cứu tinh! TickerMode giống như một DJ chuyên nghiệp, nó có thể 'cut' nhạc (animation) ở một khu vực nhất định trong 'club' (UI subtree) nếu khu vực đó đang trống hoặc không cần thiết. Khi enabled của TickerMode được set là false, nó sẽ ra lệnh cho tất cả các Ticker trong subtree của nó 'chill out' đi, đừng có 'tick' nữa. Điều này đồng nghĩa với việc các animation trong khu vực đó sẽ tạm dừng, không tiêu tốn CPU/GPU hay pin nữa. 'No cap', nó giúp app tụi em mượt mà và tiết kiệm pin hơn hẳn!
2. Code Ví Dụ Minh Hoạ Rõ Ràng (Thực chiến luôn nha!)
Để dễ hình dung, anh sẽ cho tụi em xem một ví dụ kinh điển với PageView. Tưởng tượng mỗi trang của PageView có một hình tròn đang xoay. Chúng ta chỉ muốn hình tròn ở trang hiện tại xoay thôi, còn các trang khác thì 'đứng hình' để tiết kiệm năng lượng.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TickerMode Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const TickerModeDemo(),
);
}
}
class TickerModeDemo extends StatefulWidget {
const TickerModeDemo({super.key});
@override
State<TickerModeDemo> createState() => _TickerModeDemoState();
}
class _TickerModeDemoState extends State<TickerModeDemo> {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
void initState() {
super.initState();
_pageController.addListener(() {
if (_pageController.page != null) {
setState(() {
_currentPage = _pageController.page!.round();
});
}
});
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TickerMode với PageView'),
),
body: PageView.builder(
controller: _pageController,
itemCount: 3,
itemBuilder: (context, index) {
// Đây là điểm mấu chốt: TickerMode
return TickerMode(
enabled: index == _currentPage, // Chỉ enable Ticker nếu là trang hiện tại
child: Center(
child: AnimatedRotatingCircle(
pageIndex: index,
),
),
);
},
),
);
}
}
class AnimatedRotatingCircle extends StatefulWidget {
final int pageIndex;
const AnimatedRotatingCircle({required this.pageIndex, super.key});
@override
State<AnimatedRotatingCircle> createState() => _AnimatedRotatingCircleState();
}
// Sử dụng SingleTickerProviderStateMixin để cung cấp Ticker cho AnimationController
class _AnimatedRotatingCircleState extends State<AnimatedRotatingCircle>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, // 'this' ở đây là TickerProvider
duration: const Duration(seconds: 2),
)..repeat(); // Lặp lại animation vô hạn
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Thêm một Text để dễ dàng thấy trang nào đang hoạt động
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RotationTransition(
turns: _controller,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.primaries[widget.pageIndex % Colors.primaries.length],
shape: BoxShape.circle,
),
),
),
const SizedBox(height: 20),
Text(
'Trang ${widget.pageIndex + 1}',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text(
// Kiểm tra xem animation có đang chạy không
_controller.isAnimating ? 'Đang xoay...' : 'Đang nghỉ...',
style: TextStyle(
fontSize: 18,
color: _controller.isAnimating ? Colors.green : Colors.red,
),
),
],
);
}
}
Khi chạy code này, tụi em sẽ thấy chỉ hình tròn ở trang hiện tại mới xoay. Khi vuốt sang trang khác, hình tròn cũ sẽ dừng lại và hình tròn mới bắt đầu xoay. Đó là do TickerMode đã 'vibe check' và chỉ cho phép Ticker hoạt động khi index == _currentPage.

3. Mẹo (Best Practices) để ghi nhớ hoặc dùng thực tế ('Glow up' skill của tụi em)
- Ghi nhớ vai trò: Hãy nghĩ
TickerModenhư một 'energy saver mode' (chế độ tiết kiệm năng lượng) cho các widget animation. Nó không tự động 'kill' animation, mà nó 'pause' cái cơ chếtickcủa animation trong subtree của nó. Khienabled: false,AnimationControllersẽ không nhận được tín hiệuticknữa, nên nó sẽ không cập nhật trạng thái animation. - Khi nào dùng: Luôn tự hỏi: "Cái animation này có thực sự cần thiết phải chạy khi nó không hiển thị hoặc không phải là trọng tâm chú ý của người dùng không?" Nếu câu trả lời là không, hãy nghĩ đến
TickerMode. - Phân biệt với
KeepAliveClientMixin:KeepAliveClientMixingiúp giữ trạng thái của widget (ví dụ: cuộn đến đâu, dữ liệu gì) khi nó không còn hiển thị trên màn hình nữa (như các trang củaPageViewkhi vuốt qua). CònTickerModethì tập trung vào việc điều khiển animation của widget đó. Hai cái này có thể dùng chung với nhau để vừa giữ trạng thái, vừa tối ưu animation. - Không lạm dụng: Đừng dùng
TickerModecho mọi thứ. Các animation quan trọng, luôn hiển thị (như loading indicator, Hero animation) thì không nên bị tắt. Hãy dùng một cách có chiến lược.
4. Ứng dụng thực tế các app/website đã dùng (Creyt's 'war stories')
Trong thực tế phát triển, TickerMode được sử dụng rất nhiều trong các widget 'khủng' của Flutter:
PageView: Như ví dụ ở trên,PageViewtự nó đã sử dụngTickerModeđể tắt animation của các trang không hiển thị, giúp trải nghiệm cuộn mượt mà hơn và tiết kiệm tài nguyên.IndexedStack: Widget này cũng thường được dùng để hiển thị một trong nhiều widget con. Các widget con không được hiển thị sẽ được 'pause' animation quaTickerMode.Offstage: Khi một widget được đặtOffstage(tức là không hiển thị nhưng vẫn tồn tại trong cây widget), nó cũng thường đi kèm với việc tắt animation của nó để tránh lãng phí.- Các hệ thống tab/navigation tùy chỉnh: Nếu tụi em tự xây dựng một hệ thống tab phức tạp, việc sử dụng
TickerModeđể quản lý animation ở các tab không hoạt động là một 'best practice' để đảm bảo hiệu suất 'boujee' nhất.
Anh Creyt từng 'ngu ngơ' để hàng tá animation chạy loạn xạ trong một app dashboard có nhiều biểu đồ động. Kết quả là app cứ giật giật, pin điện thoại nóng ran. Sau này mới 'ngộ' ra TickerMode, áp dụng vào thì app chạy mượt như lướt ván, khách hàng cứ khen lấy khen để. 'No cap', đó là một trong những bài học đắt giá về tối ưu hiệu suất!
5. Thử nghiệm đã từng và hướng dẫn nên dùng cho case nào (Khi nào thì 'flex' TickerMode?)
Anh đã từng thử nghiệm TickerMode trong nhiều trường hợp:
- Dashboard với nhiều widget động: Khi có các biểu đồ, widget cập nhật liên tục hoặc có animation riêng biệt. Chỉ cho phép animation chạy khi widget đó đang ở trên màn hình hoặc đang được người dùng tương tác.
- Ứng dụng có nhiều bước (multi-step forms): Chỉ animation ở bước hiện tại, các bước trước và sau thì 'standby'.
- Game nhỏ tích hợp trong app: Khi game đang chạy, các animation nền của app có thể được tắt để tập trung tài nguyên cho game. Khi thoát game, các animation nền lại được kích hoạt lại.
Nên dùng TickerMode khi:
- Tụi em có một 'khu vực' UI chứa animation mà khu vực đó không phải lúc nào cũng hiển thị hoặc không phải là trọng tâm chú ý.
- Khi tụi em muốn cải thiện hiệu suất, giảm tải CPU/GPU và tiết kiệm pin cho thiết bị người dùng. Đặc biệt quan trọng với các ứng dụng có animation phức tạp hoặc chạy trên các thiết bị cấu hình không quá mạnh.
- Khi tụi em muốn có quyền kiểm soát 'granular' hơn về vòng đời animation trong các thành phần UI khác nhau.
Tóm lại, TickerMode không phải là một viên đạn bạc, nhưng nó là một công cụ cực kỳ mạnh mẽ trong tay một developer 'có tầm nhìn'. Hãy dùng nó một cách khôn ngoan để 'flex' hiệu suất app của tụi em lên một tầm cao mới nhé!
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é!