Hướng dẫn "AnimatedPhysicalModel" - Flutter
Flutter

Hướng dẫn "AnimatedPhysicalModel" - Flutter

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

3 Lượt

"AnimatedPhysicalModel"

Chào các bạn đồng nghiệp tương lai của ngành lập trình,

Hôm nay, chúng ta sẽ cùng nhau "mổ xẻ" một viên ngọc ẩn trong kho tàng animation của Flutter: AnimatedPhysicalModel. Nghe cái tên có vẻ "học thuật" và hơi "đao to búa lớn" đúng không? Đừng lo, tôi sẽ biến nó thành một câu chuyện dễ hiểu như việc bạn pha cà phê buổi sáng vậy.


AnimatedPhysicalModel: Khi UI của bạn biết "biến hình" một cách duyên dáng

Bạn đã bao giờ ước rằng các thành phần UI của mình có thể "biến hình" mượt mà từ hình dạng này sang hình dạng khác, từ phẳng lì sang nổi cộm, hay thay đổi màu sắc bóng đổ như một chú tắc kè hoa chưa? Nếu có, thì AnimatedPhysicalModel chính là "phép thuật" bạn đang tìm kiếm.

1. Khái niệm là gì và để làm gì?

Hãy hình dung thế này: bạn có một miếng đất sét ma thuật. Bạn ra lệnh cho nó: "Giờ hãy là một khối vuông, cao 5cm và bóng đổ màu xám!". Rồi sau đó, bạn lại bảo: "Không, không, giờ hãy biến thành một khối tròn, cao 15cm và bóng đổ màu xanh ngọc!". AnimatedPhysicalModel chính là "thần chú" giúp miếng đất sét đó không chỉ biến đổi ngay lập tức mà còn chuyển mình một cách mềm mại, uyển chuyển giữa hai trạng thái đó.

Trong thế giới Flutter, AnimatedPhysicalModel là một widget đặc biệt được thiết kế để tự động tạo hiệu ứng chuyển động (animation) cho các thuộc tính "vật lý" của một widget con. Các thuộc tính "vật lý" ở đây bao gồm:

  • shape: Hình dạng của widget (vuông, tròn, chữ nhật bo góc).
  • elevation: Độ "nổi" của widget so với bề mặt, tạo cảm giác 3D.
  • shadowColor: Màu sắc của bóng đổ mà elevation tạo ra.
  • borderRadius: Độ bo tròn các góc (chỉ áp dụng khi shapeBoxShape.rectangle).

Nói tóm lại, nó giúp bạn tạo ra những hiệu ứng chuyển đổi hình dạng, độ sâu, và màu bóng đổ một cách tự độngmượt mà mà không cần phải can thiệp quá sâu vào cơ chế animation phức tạp. Bạn chỉ cần định nghĩa trạng thái cuối cùng bạn muốn, và AnimatedPhysicalModel sẽ lo phần còn chuyển động tới đó. Đây là một loại "animation ngầm định" (Implicit Animation) – bạn khai báo trạng thái đích, còn nó tự lo đường đi.

Illustration

2. Code Ví Dụ Minh Hoạ Rõ Ràng, Ngầu

Để dễ hình dung, chúng ta hãy tạo một cái "card" biết biến hình khi người dùng chạm vào nó nhé. Nó sẽ biến từ một hình chữ nhật bo góc phẳng, sang một hình tròn nổi bật với bóng đổ màu khác.

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: 'AnimatedPhysicalModel Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const AnimatedShapeChanger(),
    );
  }
}

class AnimatedShapeChanger extends StatefulWidget {
  const AnimatedShapeChanger({super.key});

  @override
  State<AnimatedShapeChanger> createState() => _AnimatedShapeChangerState();
}

class _AnimatedShapeChangerState extends State<AnimatedShapeChanger> {
  // Trạng thái ban đầu của các thuộc tính
  BoxShape _currentShape = BoxShape.rectangle;
  double _currentElevation = 0.0;
  Color _currentShadowColor = Colors.grey.shade400;
  BorderRadiusGeometry _currentBorderRadius = BorderRadius.circular(8.0);

  // Phương thức để thay đổi trạng thái
  void _toggleShape() {
    setState(() {
      if (_currentShape == BoxShape.rectangle) {
        // Chuyển sang hình tròn, nổi hơn, bóng đổ đậm hơn
        _currentShape = BoxShape.circle;
        _currentElevation = 20.0;
        _currentShadowColor = Colors.deepPurple.shade700.withOpacity(0.8);
        _currentBorderRadius = BorderRadius.circular(100.0); // Không có tác dụng với BoxShape.circle nhưng vẫn giữ để minh họa
      } else {
        // Trở về hình chữ nhật bo góc, phẳng hơn
        _currentShape = BoxShape.rectangle;
        _currentElevation = 4.0;
        _currentShadowColor = Colors.green.shade400.withOpacity(0.6);
        _currentBorderRadius = BorderRadius.circular(16.0);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Biến Hình với AnimatedPhysicalModel'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: _toggleShape, // Khi chạm vào, gọi hàm thay đổi trạng thái
          child: AnimatedPhysicalModel(
            duration: const Duration(milliseconds: 600), // Thời gian chuyển động
            curve: Curves.easeOutCubic, // Loại đường cong chuyển động (tăng tốc rồi giảm tốc)
            shape: _currentShape, // Hình dạng đích
            elevation: _currentElevation, // Độ nổi đích
            shadowColor: _currentShadowColor, // Màu bóng đổ đích
            borderRadius: _currentBorderRadius, // Độ bo góc đích
            color: Colors.white, // Màu nền của widget bên trong
            child: SizedBox(
              width: 200,
              height: 200,
              child: Center(
                child: Text(
                  _currentShape == BoxShape.rectangle ? 'Chạm để biến hình!' : 'Wow! Tròn xoe!',
                  style: const TextStyle(
                    color: Colors.black87,
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                  textAlign: TextAlign.center,
                ),
              ),
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleShape,
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

Giải thích Code:

  • Chúng ta có một StatefulWidget tên là AnimatedShapeChanger để quản lý trạng thái của các thuộc tính.
  • _currentShape, _currentElevation, _currentShadowColor, và _currentBorderRadius là các biến trạng thái, sẽ thay đổi khi người dùng tương tác.
  • Hàm _toggleShape() được gọi khi người dùng chạm vào GestureDetector. Trong hàm này, chúng ta dùng setState() để cập nhật các biến trạng thái, từ đó kích hoạt AnimatedPhysicalModel tạo hiệu ứng.
  • AnimatedPhysicalModel nhận vào các thuộc tính duration (thời gian diễn ra animation), curve (kiểu animation, ví dụ Curves.easeOutCubic tạo cảm giác chuyển động tự nhiên hơn), và quan trọng nhất là các thuộc tính shape, elevation, shadowColor, borderRadius mà chúng ta muốn chuyển động đến.
  • Thuộc tính color là màu nền của chính AnimatedPhysicalModel (bên dưới child).
  • child là widget con mà AnimatedPhysicalModel sẽ "biến hình" xung quanh nó.

Khi bạn chạy ví dụ này, bạn sẽ thấy một hình chữ nhật bo góc. Mỗi lần bạn chạm vào nó, nó sẽ mượt mà chuyển thành hình tròn, nổi lên, và bóng đổ đổi màu, rồi lại quay trở lại.

3. Một vài mẹo (Best Practices) để ghi nhớ hoặc dùng thực tế

  • Nhớ cái tên "Physical": Tên gọi AnimatedPhysicalModel không phải ngẫu nhiên. Nó ám chỉ các thuộc tính vật lý của một đối tượng: hình dạng (shape, borderRadius), độ sâu (elevation), và cách nó tương tác với ánh sáng (shadowColor). Điều này giúp bạn phân biệt nó với các widget animation ngầm định khác như AnimatedContainer (thay đổi màu, kích thước, padding, margin...) hay AnimatedOpacity.
  • Khi nào dùng AnimatedPhysicalModel?
    • Ưu tiên: Khi bạn chỉ cần thay đổi các thuộc tính shape, elevation, shadowColor, borderRadius của một widget và muốn animation được xử lý tự động.
    • Tránh dùng: Nếu bạn cần kiểm soát từng khung hình của animation, hoặc animation phức tạp hơn (ví dụ: xoay, scale, di chuyển theo đường cong tùy chỉnh), bạn sẽ cần đến AnimationController và các widget AnimatedBuilder hoặc TweenAnimationBuilder.
  • Hiệu suất (Performance): Mặc dù Flutter rất tối ưu, nhưng việc liên tục thay đổi elevation hoặc borderRadius với giá trị lớn có thể tốn tài nguyên hơn một chút so với các animation đơn giản khác. Sử dụng curve phù hợp (ví dụ Curves.easeOut, Curves.easeInOut) để animation trông tự nhiên và mượt mà, che đi những chi tiết nhỏ nếu có.
  • borderRadiusshape: Hãy nhớ rằng borderRadius chỉ có tác dụng khi shapeBoxShape.rectangle. Nếu shapeBoxShape.circle, borderRadius sẽ bị bỏ qua. Tuy nhiên, bạn vẫn có thể khai báo nó để khi shape chuyển về rectangle thì nó sẽ có hiệu lực ngay lập tức với animation.
  • Kết hợp với GestureDetector: Như ví dụ trên, AnimatedPhysicalModel thường được đặt làm con của một GestureDetector hoặc InkWell để dễ dàng kích hoạt animation thông qua các tương tác của người dùng.
  • Độ "ngầu" của curve: Đừng coi thường thuộc tính curve. Nó quyết định "cảm giác" của animation. Curves.easeOutCubic tạo cảm giác nhanh lúc đầu rồi chậm dần, rất tự nhiên. Thử nghiệm với Curves.bounceOut, Curves.elasticOut để có những hiệu ứng "nhún nhảy" độc đáo hơn.

Như vậy, AnimatedPhysicalModel không chỉ là một công cụ tiện lợi mà còn là một "nghệ sĩ" thầm lặng, giúp các thành phần UI của bạn trở nên sống động và tương tác hơn, biến những thay đổi khô khan thành những chuyển động duyên dáng. Hãy thử nghiệm và thêm chút "phép thuật" này vào các ứng dụng của bạn nhé! Chúc các bạn code vui vẻ và sáng tạo!

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!