
Chào các GenZ developer tương lai, và cả những chiến thần code đã lăn lộn trên chiến trường! Anh Creyt lại xuất hiện để khai sáng cho các em một khái niệm tưởng chừng nhỏ bé nhưng lại cực kỳ quan trọng trong việc "dụ dỗ" người dùng ở lại app của mình: TabPageSelector trong Flutter.
TabPageSelector: Mắt Thần Của Giao Diện, Dẫn Lối GenZ Không Lạc Lối!
Các em cứ hình dung thế này, khi các em lướt TikTok, xem story trên Instagram, hay thậm chí là xem mấy cái quảng cáo "swipe-up" trên app nào đó, có phải đôi khi các em thấy mấy cái chấm tròn nhỏ xíu ở đâu đó trên màn hình không? Mấy cái chấm đó thay đổi màu sắc, to nhỏ tùy theo việc các em đang ở trang nào, slide nào. Chính xác! TabPageSelector trong Flutter chính là "mấy cái chấm thần thánh" đó.
Nói một cách hàn lâm hơn nhưng vẫn dễ hiểu, TabPageSelector là một widget "chuyên gia chỉ điểm". Nó không tự mình làm gì cả, không có khả năng điều khiển hay chuyển trang. Nhiệm vụ duy nhất của nó là "nhìn" vào một TabController hoặc PageController (cái này mới là "ông chủ" thực sự điều khiển các trang), và sau đó "báo hiệu" cho người dùng biết hiện tại họ đang đứng ở vị trí nào trong chuỗi các trang đó. Nó giống như cái đèn tín hiệu trên bảng điều khiển xe hơi vậy, chỉ báo hiệu chứ không lái xe.
Để làm gì? Hay, "Tại sao mình cần nó, anh Creyt?"
Đơn giản là để cải thiện trải nghiệm người dùng (UX) một cách thần sầu.
- Chỉ dẫn trực quan: Người dùng sẽ biết ngay họ đang ở trang 1 trong 5 trang, hay trang cuối cùng rồi, không còn cảm giác "lạc trôi" giữa biển thông tin.
- Tăng tương tác: Khi người dùng thấy có nhiều trang, họ có xu hướng vuốt xem hết hơn, đặc biệt là trong các màn hình onboarding (giới thiệu ứng dụng) hay gallery ảnh sản phẩm.
- Thẩm mỹ: Một hàng chấm nhỏ xinh xắn, được tùy chỉnh màu sắc, kích thước hợp lý sẽ làm giao diện của em trông chuyên nghiệp và "có gu" hơn hẳn.
Code Ví Dụ Minh Họa: "Thực chiến" ngay và luôn!
Để TabPageSelector hoạt động, em cần một "ông chủ" là TabController (hoặc PageController cho PageView). Ở đây, anh sẽ dùng DefaultTabController để mọi thứ đơn giản như ăn kẹo.
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: 'TabPageSelector Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TabPageSelectorScreen(),
);
}
}
class TabPageSelectorScreen extends StatefulWidget {
const TabPageSelectorScreen({super.key});
@override
State<TabPageSelectorScreen> createState() => _TabPageSelectorScreenState();
}
class _TabPageSelectorScreenState extends State<TabPageSelectorScreen> with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<Color> _pageColors = [
Colors.redAccent,
Colors.greenAccent,
Colors.blueAccent,
Colors.purpleAccent,
];
@override
void initState() {
super.initState();
_tabController = TabController(length: _pageColors.length, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TabPageSelector Của Creyt'),
bottom: TabBar( // Dùng TabBar ở đây nếu muốn có tabs truyền thống
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.search)),
Tab(icon: Icon(Icons.settings)),
Tab(icon: Icon(Icons.person)),
],
),
),
body: Column(
children: [
Expanded(
child: TabBarView(
controller: _tabController,
children: _pageColors.asMap().entries.map((entry) {
int index = entry.key;
Color color = entry.value;
return Container(
color: color,
child: Center(
child: Text(
'Trang số ${index + 1}',
style: const TextStyle(fontSize: 30, color: Colors.white),
),
),
);
}).toList(),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TabPageSelector(
controller: _tabController, // KẾT NỐI VỚI "ÔNG CHỦ" Ở ĐÂY!
selectedColor: Colors.deepOrange, // Màu chấm khi được chọn
color: Colors.grey.shade400, // Màu chấm khi không được chọn
indicatorSize: 12.0, // Kích thước của mỗi chấm
),
),
const SizedBox(height: 20),
],
),
);
}
}
Giải Thích Code: "Mổ xẻ" ra xem nó có gì!
_tabController = TabController(...): Đây là trái tim của mọi thứ. Anh khởi tạo mộtTabControllervớilengthbằng số lượng trang (ở đây là 4 màu).vsync: thislà cần thiết để animation hoạt động mượt mà, và thường được cung cấp bởiSingleTickerProviderStateMixinmà anhwithvào_TabPageSelectorScreenState.TabBarView(...): Đây là nơi chứa các trang thực tế của em. Nó sẽ hiển thị từngContainervới màu sắc khác nhau. Quan trọng là nó cũng được gắn với_tabController.TabPageSelector(...): Đây là ngôi sao của chúng ta!controller: _tabController: Đây là lúcTabPageSelector"bắt tay" vớiTabControllerđể biết được trạng thái hiện tại. Nó sẽ "theo dõi" ông chủ của nó.selectedColor,color: Tùy chỉnh màu sắc cho chấm đang được chọn và các chấm còn lại. Giúp app của em "đẹp trai" hơn.indicatorSize: Kích thước của mỗi chấm. Điều chỉnh cho phù hợp với thiết kế của em.
Khi em vuốt qua lại giữa các trang trong TabBarView, em sẽ thấy TabPageSelector tự động đổi màu chấm tương ứng, báo hiệu em đang ở trang nào. Tuyệt vời chưa?

Mẹo Hay Từ Creyt (Best Practices): "Để Code Không Chỉ Chạy Mà Còn Bay!"
- Luôn đi kèm với "ông chủ":
TabPageSelectorvô dụng nếu không cóTabControllerhoặcPageController. Hãy đảm bảo chúng được kết nối đúng cách. - Tùy biến hết cỡ: Đừng ngại thay đổi
selectedColor,color,indicatorSize. Đây là những props nhỏ nhưng tạo ra sự khác biệt lớn về mặt thẩm mỹ và nhận diện thương hiệu. - Không dùng cho TabBar truyền thống: Nếu em muốn người dùng nhấn vào các chấm để chuyển tab, thì đó không phải là việc của
TabPageSelector. Lúc đó, em cần dùngTabBarwidget (như anh có đặt tạm trongAppBarví dụ trên) hoặc tự xây dựng widget riêng.TabPageSelectorlà để hiển thị thôi, không phải để tương tác. - Cân nhắc ngữ cảnh: Nó cực kỳ hiệu quả cho các màn hình giới thiệu (onboarding), gallery ảnh, hoặc các bước trong một quy trình (ví dụ: đăng ký nhiều bước).
Ứng Dụng Thực Tế: "Ai đang dùng nó ngoài kia?"
Em có thể thấy TabPageSelector (hoặc các biến thể của nó) ở rất nhiều nơi:
- Màn hình Onboarding: Khi em cài app mới, thường có vài trang giới thiệu tính năng. Mấy cái chấm dưới cùng chính là nó đó.
- Gallery ảnh sản phẩm: Trên các app thương mại điện tử, khi em xem nhiều ảnh của một sản phẩm, thường có chấm tròn báo hiệu em đang xem ảnh số mấy.
- Stories trên mạng xã hội: Mặc dù không phải lúc nào cũng là chấm tròn, nhưng cái ý tưởng "hiển thị tiến độ/vị trí" là tương tự.
- Các bước điền form: Một form có nhiều bước, mỗi bước là một trang, và các chấm tròn giúp người dùng biết họ đang ở bước nào.
Thử Nghiệm Đã Từng và Lời Khuyên Nên Dùng Cho Case Nào: "Kinh nghiệm xương máu của anh Creyt!"
Anh Creyt đã từng "lỡ tay" dùng TabPageSelector ở những nơi không phù hợp. Ví dụ, cố gắng biến nó thành một TabBar có thể bấm được. Kết quả là mất thời gian, code phức tạp và người dùng thì bối rối.
Nên dùng khi:
- Em có một
PageViewhoặcTabBarViewmà em muốn người dùng vuốt để chuyển trang, và chỉ cần một chỉ báo trực quan về vị trí hiện tại. - Các màn hình giới thiệu sản phẩm/ứng dụng (onboarding flows).
- Các gallery ảnh, album.
- Màn hình hướng dẫn từng bước mà không cần người dùng phải bấm vào các bước để nhảy cóc.
Không nên dùng khi:
- Em muốn người dùng tương tác trực tiếp với các chỉ báo (ví dụ, bấm vào chấm thứ 3 để nhảy đến trang 3). Lúc này,
TabBarhoặc các custom navigation widget mới là chân ái. - Số lượng trang quá lớn (ví dụ, 20-30 trang). Một hàng dài chấm chấm sẽ trông rất rối mắt và không hiệu quả. Lúc đó, có lẽ em cần một cách điều hướng khác như danh sách hoặc menu.
Nhớ nhé các em, TabPageSelector không phải là một chiến binh mạnh mẽ tự thân, mà nó là một "trợ lý đắc lực" giúp "ông chủ" TabController hoặc PageController tỏa sáng, mang lại trải nghiệm mượt mà và trực quan cho người dùng. Nắm vững nó, và giao diện của em sẽ "hack não" người dùng một cách tích cực đấy! Cố lên!
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é!