Một nút đã fade đến mức không còn nhìn thấy nhưng vùng của nó vẫn có thể nhận chạm. Nếu chỉ thay opacity, bạn mới thay phần hiển thị. Quy tắc xác định widget có được chọn làm đích nhận sự kiện hay không là một phần khác của giao diện.
Bài đối chiếu Flutter API ngày 10/11/2026. Ví dụ Material được rà theo tài liệu và cấu trúc widget; chưa chạy Flutter SDK, build, widget test, animation trên thiết bị hoặc trình đọc màn hình. Khi đưa vào dự án, cần thực hiện các ca kiểm tra dưới đây trên phiên bản SDK đang dùng.
Một trạng thái, ba trách nhiệm
AnimatedOpacity tự chuyển độ trong suốt khi giá trị opacity đổi, nhưng opacity 0 không ngăn hit testing của các widget con. IgnorePointer giải quyết phần nhận sự kiện con trỏ khi ignoring bằng true.
Nếu giao diện thật sự muốn phần đã ẩn không xuất hiện trong cây semantics, có thể cấu hình riêng bằng ExcludeSemantics. Không mặc định việc bỏ chạm cũng đã loại toàn bộ mô tả cho công cụ hỗ trợ tiếp cận. Trong ví dụ, cả ba lựa chọn cùng dựa vào biến hidden để đọc được chính sách ở một nơi.
Một nút ẩn và một nút điều khiển ở bên ngoài
Thử code sau trong lib/main.dart của dự án Material:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: FadeDemo()));
class FadeDemo extends StatefulWidget {
const FadeDemo({super.key});
@override
State<FadeDemo> createState() => _FadeDemoState();
}
class _FadeDemoState extends State<FadeDemo> {
bool hidden = false;
int presses = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Fade và tương tác')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () => setState(() => hidden = !hidden),
child: Text(hidden ? 'Hiện nút' : 'Ẩn nút'),
),
const SizedBox(height: 24),
ExcludeSemantics(
excluding: hidden,
child: IgnorePointer(
ignoring: hidden,
child: AnimatedOpacity(
opacity: hidden ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: ElevatedButton(
onPressed: () => setState(() => presses += 1),
child: const Text('Nút đang fade'),
),
),
),
),
const SizedBox(height: 24),
Text('Số lần nhận nhấn: $presses'),
],
),
),
);
}
}
Nút điều khiển nằm ngoài vùng bị ẩn, nên người thử còn đường bật nó trở lại. Bộ đếm cũng ở bên ngoài để quan sát thao tác có được nhận hay không. Thời lượng 200 ms chỉ là cấu hình minh họa, không phải kết quả tối ưu animation đã đo.
Chặn ngay khi state đổi hay chờ fade kết thúc?
Khi hidden thành true, IgnorePointer bắt đầu bỏ qua con trỏ ngay theo state, trong khi opacity có thể còn đang chuyển dần. Khi hiện lại, ignoring trở về false ngay. Đây là chính sách của demo: không đợi mốc kết thúc animation để quyết định khả năng chạm.
Nếu sản phẩm yêu cầu chỉ cho thao tác sau khi fade-in xong, cần một trạng thái tương tác hoặc quy trình đồng bộ khác. Đừng mô tả snippet này như đã triển khai chính sách ấy. Việc vẽ và việc nhận sự kiện có thể cần những mốc chuyển khác nhau, nên hãy ghi yêu cầu trước khi thêm callback animation.
Giữ chỗ khác với bỏ widget khỏi layout
Demo giữ widget trong cây để làm fade. Vì vậy, khi nút không nhìn thấy, vùng bố cục của nó vẫn còn trong cấu trúc này. IgnorePointer không có nhiệm vụ thu gọn khoảng trống. Nếu mục tiêu là các phần bên dưới dịch lên, bạn cần thiết kế thêm cách chuyển bố cục, thay vì chỉ đổi độ trong suốt.
Tài liệu cũng nhắc chi phí vẽ của opacity animation. Bài không có profile để khẳng định ảnh hưởng trên thiết bị cụ thể. Đừng thêm nhiều lớp opacity hoặc mở rộng demo thành một hiệu ứng phức tạp rồi dùng ví dụ này làm bằng chứng hiệu năng đã đạt.
Kế hoạch kiểm tra
Khi nút đang hiện, bấm vài lần và xem bộ đếm tăng. Chuyển sang ẩn rồi thử chạm tại vị trí cũ, cả trong lúc fade-out lẫn sau khi hoàn tất; bộ đếm không nên tăng theo chính sách demo. Hiện lại và kiểm tra thao tác có thể nhận trở lại.
Tiếp theo, kiểm tra semantics bằng công cụ của nền tảng để xác nhận phần hidden được loại theo ExcludeSemantics. Thử thao tác bàn phím và focus nếu ứng dụng hỗ trợ chúng; IgnorePointer không phải lời cam kết xử lý mọi cơ chế focus. Các bước này cần được thực hiện thực tế, chưa được bài báo cáo là đã chạy.
Tách ba trách nhiệm giúp một hiệu ứng biến mất có hành vi rõ ràng. Đặt cùng state cho chúng khi yêu cầu đồng bộ, và chỉ tách mốc chuyển khi thiết kế tương tác thật sự cần điều đó.

