
Chào mừng các bạn đến với buổi học hôm nay! Hôm nay, chúng ta sẽ cùng nhau 'giải mã' một cặp đôi widget cực kỳ quyền năng trong Flutter, đó là CompositedTransformTarget và CompositedTransformFollower. Nghe tên có vẻ 'hack não' nhỉ? Đừng lo, tôi sẽ biến nó thành chuyện đơn giản như ăn kẹo, hay nói đúng hơn là như việc bạn dùng GPS để tìm đường vậy.
1. CompositedTransformTarget là gì và để làm gì?
Hãy tưởng tượng thế này: bạn đang ở trong một thành phố rộng lớn (ứng dụng Flutter của bạn), và bạn muốn đặt một cái 'đèn hiệu' (beacon) ở một vị trí cụ thể. Sau đó, bạn muốn một vật thể khác (ví dụ: một chiếc máy bay không người lái) luôn luôn bay theo và giữ một khoảng cách nhất định so với cái đèn hiệu đó, bất kể cái đèn hiệu đó có di chuyển hay cả thành phố có 'biến hình' (phóng to, thu nhỏ, xoay). Cái đèn hiệu đó chính là CompositedTransformTarget.
Nói một cách kỹ thuật hơn, CompositedTransformTarget là một widget đóng vai trò là điểm tham chiếu trong cây widget của bạn. Nó không tự hiển thị gì cả, mà chỉ đơn thuần là một 'mốc tọa độ' mà các widget khác, cụ thể là CompositedTransformFollower, có thể 'bám' vào để định vị chính xác vị trí của mình.
Mục đích chính? Khi bạn cần hiển thị một overlay (một thành phần nổi lên trên tất cả các nội dung khác, ví dụ như tooltip, dropdown menu, context menu) mà vị trí của nó phụ thuộc vào một widget khác nằm sâu trong cây widget, và hai widget này không chung một Stack hay cùng một RenderObject cha. Đây là lúc CompositedTransformTarget tỏa sáng!
Nó giải quyết bài toán định vị 'xuyên không gian' (xuyên qua các cây widget khác nhau, thậm chí xuyên qua các OverlayEntry), đảm bảo rằng widget 'theo sau' luôn được đặt đúng chỗ một cách hiệu quả về mặt hiệu năng.
Để làm được điều này, chúng ta cần một 'sợi dây liên kết' bí mật, đó chính là LayerLink. CompositedTransformTarget và CompositedTransformFollower sẽ cùng nắm giữ một LayerLink để 'nhận diện' và 'kết nối' với nhau.

2. Code Ví Dụ Minh Họa: Tạo một Dropdown Menu đơn giản
Hãy cùng xây dựng một ví dụ thực tế: một nút bấm mà khi nhấn vào, một dropdown menu sẽ hiện ra ngay bên dưới nó, bất kể nút bấm đó nằm ở đâu trên màn hì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: 'CompositedTransformTarget Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final LayerLink _layerLink = LayerLink(); // Sợi dây liên kết
OverlayEntry? _overlayEntry; // Overlay để chứa dropdown
void _showOverlay(BuildContext context) {
if (_overlayEntry == null) {
_overlayEntry = OverlayEntry(
builder: (context) => CompositedTransformFollower(
link: _layerLink, // Nắm cùng sợi dây với Target
showWhenUnlinked: false, // Ẩn khi không còn liên kết
offset: const Offset(0.0, 50.0), // Dịch xuống 50px so với Target
targetAnchor: Alignment.bottomLeft, // Gốc của Target là góc dưới bên trái
followerAnchor: Alignment.topLeft, // Gốc của Follower là góc trên bên trái
child: Material(
elevation: 8.0,
child: SizedBox(
width: 200, // Chiều rộng của dropdown
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(title: const Text('Option 1'), onTap: _hideOverlay),
ListTile(title: const Text('Option 2'), onTap: _hideOverlay),
ListTile(title: const Text('Option 3'), onTap: _hideOverlay),
],
),
),
),
),
);
Overlay.of(context).insert(_overlayEntry!); // Chèn Overlay vào màn hình
}
}
void _hideOverlay() {
_overlayEntry?.remove(); // Gỡ Overlay khỏi màn hình
_overlayEntry = null;
}
@override
void dispose() {
_overlayEntry?.remove(); // Đảm bảo Overlay được gỡ khi widget bị hủy
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Demo CompositedTransformTarget')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 100), // Khoảng trống để thấy hiệu ứng cuộn
// Nút bấm của chúng ta, được bọc bởi CompositedTransformTarget
CompositedTransformTarget(
link: _layerLink, // Đặt đèn hiệu với sợi dây này
child: ElevatedButton(
onPressed: () {
if (_overlayEntry == null) {
_showOverlay(context);
} else {
_hideOverlay();
}
},
child: const Text('Show Dropdown'),
),
),
const SizedBox(height: 200),
const Text('Cuộn xuống để thấy nút vẫn hoạt động!'),
const SizedBox(height: 300), // Thêm khoảng trống để cuộn
],
),
),
);
}
}
Giải thích Code:
LayerLink _layerLink = LayerLink();: Chúng ta khởi tạo mộtLayerLink, đây là 'sợi dây' để kết nốiTargetvàFollower.CompositedTransformTarget(link: _layerLink, child: ElevatedButton(...)): NútElevatedButtoncủa chúng ta được bọc trongCompositedTransformTarget. Widget này 'đánh dấu' vị trí của nút bấm trên màn hình._showOverlay(context): Hàm này tạo mộtOverlayEntry.OverlayEntrylà cách Flutter cho phép bạn 'chèn' các widget lên trên tất cả các widget khác trong ứng dụng.CompositedTransformFollower(link: _layerLink, ...): Bên trongOverlayEntry, chúng ta đặtCompositedTransformFollower. Nó nhận cùng_layerLinkđể biết mình phải 'bám' vào đâu.offset: const Offset(0.0, 50.0): Dịch chuyểnFollowerxuống 50 pixel theo trục Y so với vị trí được tính toán.targetAnchor: Alignment.bottomLeft: Chỉ định rằng điểm neo trênTargetlà góc dưới bên trái của nó.followerAnchor: Alignment.topLeft: Chỉ định rằng điểm neo trênFollowerlà góc trên bên trái của nó. Kết hợp hai cái này,Followersẽ được đặt sao cho góc trên bên trái của nó trùng với góc dưới bên trái củaTarget, tạo hiệu ứng dropdown xuất hiện ngay dưới nút.
_hideOverlay(): Gỡ bỏOverlayEntrykhi không cần nữa.
3. Mẹo (Best Practices) để sử dụng hiệu quả
- Quản lý
LayerLinkcẩn thận: Khởi tạoLayerLinktronginitStatecủaStatefulWidgetvà đảm bảo rằngOverlayEntrychứaCompositedTransformFollowerđượcremove()khi widget cha bịdispose()để tránh rò rỉ bộ nhớ hoặc lỗi hiển thị. - Hiểu rõ
targetAnchorvàfollowerAnchor: Đây là hai thuộc tính 'ma thuật' quyết định cáchFollowerđược căn chỉnh so vớiTarget. Hãy thử nghiệm với các giá trị nhưAlignment.topLeft,Alignment.center,Alignment.bottomRightđể đạt được hiệu ứng mong muốn.offsetchỉ là điều chỉnh nhỏ sau khi đã căn chỉnh bằng anchors. - Sử dụng
OverlayEntrycho các thành phần động:CompositedTransformFollowerthường đi đôi vớiOverlayEntryđể tạo các thành phần UI 'nổi' lên trên toàn bộ ứng dụng, không bị ảnh hưởng bởi các widget cha khác. showWhenUnlinked: ĐặtshowWhenUnlinked: falselà một thực hành tốt. Điều này đảm bảo rằngFollowersẽ tự động ẩn đi nếuTargetbị gỡ khỏi cây widget hoặc không còn được liên kết, tránh các thành phần 'lơ lửng' không mong muốn.- Khi nào thì dùng, khi nào thì không? Nếu bạn chỉ cần định vị các widget trong cùng một
Stackhoặc trong cùng mộtRenderBoxcha, hãy dùngStack,Align,Positionedthông thường.CompositedTransformTargetlà giải pháp cho các trường hợp phức tạp hơn, 'xuyên không gian' như đã nói ở trên.
4. Ứng dụng thực tế: Những nơi bạn đã thấy 'GPS' này hoạt động
Bạn có thể không nhận ra, nhưng CompositedTransformTarget và Follower đang hoạt động âm thầm trong rất nhiều ứng dụng và website bạn dùng hàng ngày:
- Dropdown Menu của Google Docs/Sheets: Khi bạn click vào một menu trên thanh công cụ, một danh sách tùy chọn hiện ra ngay bên dưới nó. Dù bạn có cuộn trang hay phóng to, menu vẫn giữ nguyên vị trí tương đối với nút bấm.
- Tooltips trên các website: Khi bạn rê chuột qua một icon nhỏ, một hộp thoại thông tin (tooltip) hiện ra ngay cạnh icon đó. Vị trí của tooltip được neo vào icon, không phải toàn bộ trang.
- Autocomplete/Suggestion Box: Khi bạn gõ vào ô tìm kiếm, một danh sách các gợi ý hiện ra ngay bên dưới ô nhập liệu. Danh sách này 'theo sát' ô tìm kiếm, kể cả khi bạn cuộn trang.
- Context Menu (Menu chuột phải): Trên các ứng dụng desktop hoặc web, khi bạn click chuột phải vào một đối tượng, một menu nhỏ hiện ra ngay tại vị trí con trỏ chuột. Vị trí menu được neo vào điểm click.
- Floating Action Button (FAB) với các tùy chọn mở rộng: Trong một số ứng dụng Flutter, khi bạn nhấn vào FAB, một vài icon nhỏ khác 'bung' ra xung quanh nó. Vị trí của các icon này được neo vào FAB.
Hy vọng qua buổi học này, các bạn đã nắm vững được sức mạnh và cách sử dụng CompositedTransformTarget một cách hiệu quả. Hãy nhớ, nó là 'GPS' giúp các widget của bạn tìm thấy nhau trong thế giới Flutter rộng 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é!