
Chào các "thợ code" tương lai, hôm nay chúng ta sẽ mổ xẻ một công cụ khá hay ho trong hộp đồ nghề Flutter mà nhiều khi các bạn bỏ qua: FractionalTranslation. Nghe tên có vẻ học thuật, nhưng thực ra nó là một "chiếc đòn bẩy" cực kỳ linh hoạt để dịch chuyển các widget của chúng ta.
FractionalTranslation là gì và để làm gì?
Thầy Creyt hay ví von thế này: Bạn có một bức tranh treo tường. Bình thường, bạn sẽ nói "đẩy bức tranh sang phải 10cm" đúng không? Đó là cách chúng ta dùng Transform.translate hoặc Positioned với các giá trị tuyệt đối (pixel). Nhưng nếu bạn muốn nói "đẩy bức tranh sang phải một nửa chiều rộng của chính nó", hoặc "kéo nó lên trên một phần tư chiều cao của nó" thì sao? Đó chính là lúc FractionalTranslation tỏa sáng!
FractionalTranslation là một widget trong Flutter cho phép bạn dịch chuyển con của nó (child widget) tương đối so với kích thước của chính con đó. Thay vì dùng pixel, bạn dùng các giá trị phân số (fraction) từ 0.0 đến 1.0 (hoặc hơn) để định vị.
Để làm gì ư? Nó cực kỳ hữu ích khi bạn muốn tạo ra các hiệu ứng UI động, responsive, hay các animation mà vị trí dịch chuyển cần phải tự động điều chỉnh theo kích thước của widget. Ví dụ, một menu trượt vào từ cạnh màn hình, một thành phần UI tự động căn chỉnh khi kích thước màn hình thay đổi, hoặc hiệu ứng parallax tinh tế.
Thuộc tính chính của nó là translation, nhận một đối tượng Offset.
Offset(0.5, 0): Dịch sang phải 50% chiều rộng của child.Offset(-0.25, 0): Dịch sang trái 25% chiều rộng của child.Offset(0, 1.0): Dịch xuống dưới 100% chiều cao của child.Offset(0.5, 0.5): Dịch sang phải 50% chiều rộng VÀ xuống dưới 50% chiều cao của child.

Code Ví Dụ Minh Họa: Một Widget "Lướt" Vào Mượt Mà
Để các bạn dễ hình dung, chúng ta sẽ tạo một widget đơn giản, khi bấm nút thì nó sẽ "lướt" từ bên ngoài vào giữa màn hình, rồi lướt ra khi bấm lại. Toàn bộ quá trình dịch chuyển sẽ dựa trên kích thước của chính nó.
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: 'FractionalTranslation Demo',
theme: ThemeData(primarySwatch: Colors.blueGrey),
home: const FractionalTranslationScreen(),
);
}
}
class FractionalTranslationScreen extends StatefulWidget {
const FractionalTranslationScreen({super.key});
@override
State<FractionalTranslationScreen> createState() => _FractionalTranslationScreenState();
}
class _FractionalTranslationScreenState extends State<FractionalTranslationScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 700),
);
// Animation starts from Offscreen (1.0 means 100% of its width to the right)
// and ends at its original position (0.0).
_animation = Tween<Offset>(
begin: const Offset(1.0, 0.0), // Start 100% of its width to the right
end: Offset.zero, // End at its original position
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleAnimation() {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FractionalTranslation Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Sử dụng AnimatedBuilder để rebuild widget khi animation thay đổi giá trị
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return FractionalTranslation(
translation: _animation.value, // Giá trị offset thay đổi theo animation
child: Material(
elevation: 8.0,
borderRadius: BorderRadius.circular(12.0),
child: Container(
width: 200, // Kích thước cố định để dễ hình dung
height: 100,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.teal.shade300,
borderRadius: BorderRadius.circular(12.0),
),
child: const Text(
'Xin chào, tôi lướt đây!',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
},
),
const SizedBox(height: 40),
ElevatedButton(
onPressed: _toggleAnimation,
child: const Text('Bật/Tắt Hiệu Ứng Lướt'),
),
],
),
),
);
}
}
Giải thích:
- Chúng ta dùng
AnimationControllerđể điều khiển tiến trình animation. Tween<Offset>được tạo vớibegin: const Offset(1.0, 0.0)vàend: Offset.zero. Điều này có nghĩa là widget sẽ bắt đầu dịch chuyển từ vị trí 100% chiều rộng của nó sang phải (ngoài màn hình, nếu nó nằm trong mộtRowhoặcStacklớn hơn) và kết thúc ở vị trí ban đầu của nó (không dịch chuyển).AnimatedBuilderlắng nghe sự thay đổi của_animationvà rebuildFractionalTranslationvới giá trịtranslationmới.FractionalTranslationnhận_animation.valuelàm thuộc tínhtranslation, khiếnContainercon của nó dịch chuyển mượt mà.
Mẹo Hay và Best Practices từ Thầy Creyt
- Nghĩ theo tỷ lệ, không phải pixel: Đây là "bí kíp" lớn nhất. Khi bạn cần một widget dịch chuyển một cách tương đối với chính nó hoặc với một container lớn hơn, hãy nghĩ ngay đến
FractionalTranslation. Nó giúp code của bạn linh hoạt và dễ bảo trì hơn rất nhiều khi giao diện thay đổi kích thước. - Kết hợp với Animation:
FractionalTranslation"sinh ra" là để làm bạn với các animation. Sử dụngAnimatedBuilderhoặcTweenAnimationBuilderđể tạo ra các hiệu ứng dịch chuyển mượt mà, tự nhiên. - Cẩn thận với Clipping: Đôi khi, khi dịch chuyển một widget ra khỏi giới hạn của cha nó, bạn có thể thấy nó bị cắt (clipped). Nếu muốn nó vẫn hiển thị đầy đủ, hãy đảm bảo widget cha không có thuộc tính
clipBehaviorlàClip.hardEdgehoặc bạn có thể bọc nó trongOverflowBoxnếu cần hiển thị ngoài giới hạn. - So sánh với
Transform.translate:Transform.translatecũng dịch chuyển widget, nhưng nó dùng giá trị pixel tuyệt đối.FractionalTranslationdùng giá trị phân số. Chọn cái nào tùy thuộc vào yêu cầu: dịch chuyển cố định một lượng pixel hay dịch chuyển tương đối theo kích thước.
Ứng Dụng Thực Tế
FractionalTranslation (hoặc các kỹ thuật tương tự dựa trên dịch chuyển tương đối) được ứng dụng rất nhiều trong các sản phẩm thực tế:
- Hiệu ứng Parallax Scrolling: Trong các trang web hoặc ứng dụng có hiệu ứng cuộn parallax, các lớp nội dung khác nhau sẽ di chuyển với tốc độ (tỷ lệ) khác nhau khi người dùng cuộn.
FractionalTranslationcó thể giúp mô phỏng điều này bằng cách dịch chuyển các lớp dựa trên vị trí cuộn và kích thước của chúng. - Slide-in Menus/Drawers: Mặc dù Flutter có
Drawerwidget riêng, nhưng để tạo các menu tùy chỉnh trượt vào từ cạnh màn hình (như các ứng dụng tin tức, mạng xã hội) với hiệu ứng tinh tế,FractionalTranslationcó thể được dùng để kiểm soát vị trí trượt dựa trên chiều rộng của menu. - Onboarding Screens/Walkthroughs: Khi bạn thấy các phần tử UI dịch chuyển vào/ra màn hình một cách mượt mà trong các màn hình giới thiệu ứng dụng lần đầu, đó thường là sự kết hợp của animation và các kỹ thuật dịch chuyển tương đối.
- Responsive UI Elements: Trong một bố cục responsive, bạn có thể muốn một nút bấm hoặc một banner quảng cáo dịch chuyển một khoảng nhất định tính theo tỷ lệ của màn hình hoặc của chính nó, thay vì một giá trị pixel cố định có thể bị lệch trên các thiết bị khác nhau.
Nhớ nhé, lập trình không chỉ là viết code, mà là "điêu khắc" logic và giao diện. FractionalTranslation là một trong những "dụng cụ" tinh xảo giúp bạn làm điều đó. Chúc các bạn code 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é!