Ê mấy đứa, hôm nay anh Creyt lại mang đến một món đồ chơi "hack não" nhưng mà cực kỳ "chill" trong thế giới Flutter đây: ScrollableState!
ScrollableState là gì mà "ghê gớm" vậy anh Creyt?
Tưởng tượng nha, cái màn hình điện thoại của tụi bây bé tí, mà nội dung thì dài dằng dặc như cuốn tiểu thuyết ngôn tình 1000 chương vậy. Để xem hết, tụi bây phải "cuộn", đúng không? Cái thằng ScrollableState này chính là 'linh hồn', là 'bộ não' đằng sau tất cả những thứ có thể cuộn trong app Flutter của tụi bây.
Từ cái ListView lướt feed Facebook, GridView xem ảnh Instagram, hay thậm chí là SingleChildScrollView để đọc một bài blog dài ngoằng – tất cả đều có một ScrollableState ngầm điều hành. Nó là cái 'trạng thái nội bộ' (internal state) mà Flutter dùng để quản lý mọi thứ liên quan đến việc cuộn. Nó giống như cái camera trong game nhập vai của tụi bây vậy, luôn biết nhân vật đang ở đâu, nhìn về hướng nào để hiển thị cảnh quan cho đúng.
Nó làm được những gì?
ScrollableState không chỉ đơn thuần là biết 'mày đang ở đâu' trên cái trang cuộn đó đâu. Không, nó 'pro' hơn nhiều! Nó nắm giữ toàn bộ thông tin về:
- Vị trí hiện tại của cuộn (scroll offset): Đang ở pixel thứ mấy từ đầu trang.
- Tốc độ cuộn: Nhanh hay chậm.
- Hướng cuộn: Đang cuộn lên hay xuống.
- Giới hạn cuộn (scroll extent): Tổng chiều dài có thể cuộn được.
- Vật lý cuộn (scroll physics): Các hiệu ứng khi cuộn chạm biên (như kéo giãn, nảy lên).
Nói chung là, mọi thứ liên quan đến việc 'di chuyển' nội dung trên màn hình, ScrollableState đều biết tuốt.
Làm sao để "nói chuyện" với ScrollableState?
Nhưng mà, cái ScrollableState này nó hơi "khép kín", nó là "internal state" của hệ thống, mình không thể trực tiếp "nói chuyện" với nó được. Vậy làm sao để mình "ra lệnh" cho nó, hay "hỏi" nó xem đang cuộn đến đâu? Đó là lúc "người đại diện" của nó xuất hiện: ScrollController!
Coi nó như cái "remote control" vạn năng của tụi bây vậy. Cứ gắn ScrollController vào bất kỳ widget nào có thể cuộn được (như ListView, GridView, SingleChildScrollView), là tụi bây có thể bắt đầu "flex" với nó rồi. Với ScrollController, tụi bây có thể:
- Kiểm tra vị trí cuộn hiện tại: Biết người dùng đang ở đâu.
- Cuộn đến một vị trí cụ thể: Dùng
animateTo(có hiệu ứng) hoặcjumpTo(tức thì). - Lắng nghe sự kiện cuộn: Biết khi nào người dùng bắt đầu cuộn, dừng cuộn, hay cuộn đến cuối trang.

Code Ví Dụ Minh Hoạ "Sương Sương"
Để dễ hình dung, anh Creyt sẽ làm một ví dụ đơn giản: một danh sách dài và một nút "Lên đầu trang" (Back to Top) chỉ hiện ra khi tụi bây cuộn xuống một đoạn nhất định.
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: 'ScrollableState Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const ScrollableStateScreen(),
);
}
}
class ScrollableStateScreen extends StatefulWidget {
const ScrollableStateScreen({super.key});
@override
State<ScrollableStateScreen> createState() => _ScrollableStateScreenState();
}
class _ScrollableStateScreenState extends State<ScrollableStateScreen> {
// 1. Khởi tạo ScrollController
final ScrollController _scrollController = ScrollController();
bool _showBackToTopButton = false; // Biến để ẩn/hiện nút "Lên đầu trang"
@override
void initState() {
super.initState();
// 2. Lắng nghe sự kiện cuộn
_scrollController.addListener(() {
// Khi người dùng cuộn xuống quá 200 pixel, hiện nút
if (_scrollController.position.pixels >= 200 && !_showBackToTopButton) {
setState(() {
_showBackToTopButton = true;
});
}
// Khi người dùng cuộn lên trên 200 pixel, ẩn nút
else if (_scrollController.position.pixels < 200 && _showBackToTopButton) {
setState(() {
_showBackToTopButton = false;
});
}
// debugPrint('Vị trí cuộn: ${_scrollController.position.pixels}');
});
}
@override
void dispose() {
// 3. Luôn luôn dispose ScrollController khi không dùng nữa
_scrollController.dispose();
super.dispose();
}
// Hàm cuộn lên đầu trang
void _scrollToTop() {
_scrollController.animateTo(
0, // Cuộn về vị trí 0 (đầu trang)
duration: const Duration(milliseconds: 500), // Trong 0.5 giây
curve: Curves.easeInOut, // Với hiệu ứng mượt mà
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ScrollableState Demo'),
),
body: ListView.builder(
// 4. Gắn ScrollController vào ListView
controller: _scrollController,
itemCount: 100, // Danh sách có 100 mục
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 4,
child: ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('Mục số ${index + 1}'),
subtitle: const Text('Đây là nội dung của một mục trong danh sách dài.'),
),
),
);
},
),
// Nút "Lên đầu trang" chỉ hiện khi _showBackToTopButton là true
floatingActionButton: _showBackToTopButton
? FloatingActionButton(
onPressed: _scrollToTop,
child: const Icon(Icons.arrow_upward),
)
: null, // Nếu không thì ẩn đi
);
}
}
Mẹo Hay và Best Practices từ Anh Creyt:
- "Dọn dẹp" sau khi chơi: Luôn nhớ gọi
_scrollController.dispose()trong hàmdispose()củaStatefulWidgetđể tránh rò rỉ bộ nhớ. Coi như chơi xong thì cất đồ chơi vào hộp vậy, gọn gàng, sạch sẽ. - Đừng "overkill": Nếu mục đích của tụi bây chỉ là biết người dùng cuộn đến đâu để ẩn/hiện một cái AppBar hay BottomNavigationBar mà không cần điều khiển cuộn, thì đôi khi
NotificationListenerlại là lựa chọn "chill" hơn, đỡ phải tạoScrollControllerlằng nhằng. Nó giống như nghe "ngóng" tiếng động xung quanh hơn là trực tiếp điều khiển vậy. - "Flex" với
ScrollPhysics: Muốn cuộn mượt mà như iOS hay "nảy" như Android?ScrollControllercho phép tụi bây tùy chỉnhScrollPhysicsđể tạo ra trải nghiệm cuộn độc đáo, đúng "vibe" app của mình. Ví dụ,BouncingScrollPhysics()cho iOS-like bounce,ClampingScrollPhysics()cho Android-like clamp. - Cẩn thận với
jumpTovàanimateTo:jumpTothì tức thì, phù hợp cho việc nhảy đến một vị trí ngay lập tức (ví dụ: chuyển tab).animateTothì mượt mà hơn, có hiệu ứng chuyển động, nhưng tốn thời gian. Chọn cái nào tùy vào tình huống nhé, đừng để người dùng "giật mình" vớijumpTokhi không cần thiết.
Ứng Dụng Thực Tế "Hơi Bị Xịn" của ScrollableState:
- Nút "Lên đầu trang" (Back to Top): Như ví dụ trên, đây là ứng dụng phổ biến nhất. Các app như Instagram, Facebook, Shopee đều có nút này khi cuộn xuống quá sâu.
- Hiệu ứng Parallax: Khi ảnh nền di chuyển chậm hơn nội dung chính, tạo cảm giác chiều sâu. Các trang web "chất chơi" hay app giới thiệu sản phẩm thường dùng cái này.
ScrollControllersẽ cung cấp offset để tính toán vị trí của các layer khác nhau. - Infinite Scroll (Cuộn vô tận): Tự động tải thêm nội dung khi người dùng cuộn gần đến cuối danh sách (ví dụ: feed của TikTok, Facebook, Twitter).
ScrollControllergiúp tụi bây biết được khi nào cần "kêu gọi" API để load thêm data. - Auto-play video khi cuộn đến: Các app video ngắn như TikTok hay YouTube Shorts sẽ tự động phát video khi nó xuất hiện trên màn hình, và tạm dừng khi cuộn đi.
ScrollControllerở đây đóng vai trò là "sensor" nhận biết vị trí và ra lệnh cho trình phát media.
Kinh Nghiệm "Xương Máu" của Anh Creyt và Nên Dùng Khi Nào?
"Anh Creyt từng 'hack' cái vụ cuộn này để tạo một cái danh sách sản phẩm vô tận (infinite scroll), cứ cuộn gần đến cuối là nó tự động load thêm sản phẩm mới. Hồi đó dùng ScrollController để lắng nghe _scrollController.position.pixels == _scrollController.position.maxScrollExtent đó. Đỉnh của chóp luôn!"
"Hoặc có lần, anh cần một cái AppBar ẩn đi khi cuộn xuống và hiện ra khi cuộn lên. Thay vì dùng SliverAppBar (cái này dễ rồi), anh 'chơi lớn' dùng ScrollController để tự tay setState cho opacity của AppBar dựa vào hướng cuộn. Hơi 'hack não' tí nhưng mà hiểu sâu hơn về cơ chế cuộn và cho phép tùy biến không giới hạn."
Khi nào nên dùng ScrollController?
- Khi tụi bây cần điều khiển trực tiếp việc cuộn (cuộn đến vị trí X, cuộn lên đầu).
- Khi tụi bây muốn lắng nghe chính xác vị trí cuộn, hướng cuộn, hoặc trạng thái cuộn để thực hiện các hành động phức tạp (như infinite scroll, parallax, hiệu ứng tùy chỉnh).
- Khi tụi bây muốn thay đổi vật lý cuộn của một widget cụ thể.
Nói chung, khi nào tụi bây muốn app của mình "thông minh" hơn, "phản ứng" với thao tác cuộn của người dùng, hoặc muốn "điều khiển" việc cuộn một cách chủ động, thì cứ nhớ đến thằng ScrollController và cái ScrollableState đằng sau nó nhé. Đừng ngại thử nghiệm, cứ "code" đi rồi "fix" sau! Chúc tụi bây "flex" với Flutter vui vẻ!
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é!