CompositedTransformTarget: GPS định vị cho widget Flutter của bạn
Flutter

CompositedTransformTarget: GPS định vị cho widget Flutter của bạn

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

15 Lượt

"CompositedTransformTarget"

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à CompositedTransformTargetCompositedTransformFollower. 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. CompositedTransformTargetCompositedTransformFollower sẽ cùng nắm giữ một LayerLink để 'nhận diện' và 'kết nối' với nhau.

Illustration

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ột LayerLink, đây là 'sợi dây' để kết nối TargetFollower.
  • CompositedTransformTarget(link: _layerLink, child: ElevatedButton(...)): Nút ElevatedButton của chúng ta được bọc trong CompositedTransformTarget. 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ột OverlayEntry. OverlayEntry là 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 trong OverlayEntry, chúng ta đặt CompositedTransformFollower. 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ển Follower xuố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ên Target là góc dưới bên trái của nó.
    • followerAnchor: Alignment.topLeft: Chỉ định rằng điểm neo trên Follower là góc trên bên trái của nó. Kết hợp hai cái này, Follower sẽ đượ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ủa Target, tạo hiệu ứng dropdown xuất hiện ngay dưới nút.
  • _hideOverlay(): Gỡ bỏ OverlayEntry khi không cần nữa.

3. Mẹo (Best Practices) để sử dụng hiệu quả

  1. Quản lý LayerLink cẩn thận: Khởi tạo LayerLink trong initState của StatefulWidget và đảm bảo rằng OverlayEntry chứa CompositedTransformFollower được remove() khi widget cha bị dispose() để tránh rò rỉ bộ nhớ hoặc lỗi hiển thị.
  2. Hiểu rõ targetAnchorfollowerAnchor: Đây là hai thuộc tính 'ma thuật' quyết định cách Follower được căn chỉnh so với Target. 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. offset chỉ là điều chỉnh nhỏ sau khi đã căn chỉnh bằng anchors.
  3. Sử dụng OverlayEntry cho các thành phần động: CompositedTransformFollower thường đi đôi với OverlayEntry để 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.
  4. showWhenUnlinked: Đặt showWhenUnlinked: false là một thực hành tốt. Điều này đảm bảo rằng Follower sẽ tự động ẩn đi nếu Target bị 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.
  5. 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 Stack hoặc trong cùng một RenderBox cha, hãy dùng Stack, Align, Positioned thông thường. CompositedTransformTarget là 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 CompositedTransformTargetFollower đ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é!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!