Flutter: Hướng dẫn Animation

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Mục lục bài viết:


Bạn sẽ học

  • Cách sử dụng các lớp cơ bản từ thư viện hoạt ảnh để thêm hoạt ảnh vào một widget.
  • Khi nào thì sử dụng AnimatedWidget và AnimatedBuilder.

Hướng dẫn này chỉ cho bạn cách tạo hoạt ảnh tường minh trong Flutter. Sau khi giới thiệu một số khái niệm, lớp và phương thức thiết yếu trong thư viện hoạt ảnh, bạn sẽ được hướng dẫn qua 5 ví dụ về hoạt ảnh. Các ví dụ được xây dựng dựa trên nhau, giới thiệu cho bạn các khía cạnh khác nhau của thư viện hoạt ảnh.

Flutter SDK cũng cung cấp hoạt ảnh chuyển động ngầm định, chẳng hạn như FadeTransitionSizeTransition, và SlideTransition. Những hoạt ảnh đơn giản này được kích hoạt bằng cách đặt điểm bắt đầu và điểm kết thúc. Chúng dễ triển khai hơn các hoạt ảnh tường minh.

Các khái niệm và lớp hoạt ảnh cơ bản

Vấn đề ở đây là gì?

  • Animation, một lớp cốt lõi trong thư viện hoạt ảnh của Flutter, nội suy các giá trị được sử dụng để hướng dẫn hoạt ảnh.
  • Một đối tượng Animation biết trạng thái hiện tại của hoạt ảnh (ví dụ: cho dù nó được bắt đầu, dừng lại hay đang di chuyển về phía trước hoặc ngược lại), nhưng không biết gì về những gì xuất hiện trên màn hình.
  • Một AnimationController để quản lý Animation.
  • Một CurvedAnimation để định nghĩa tiến trình là một đường cong phi tuyến tính.
  • Một Tween để nội suy giữa phạm vi dữ liệu được sử dụng bởi đối tượng đang được làm động. Ví dụ: Tween có thể định nghĩa một phép nội suy từ màu đỏ sang màu xanh lam hoặc từ 0 đến 255.
  • Sử dụng Listeners và StatusListeners để theo dõi các thay đổi trạng thái hoạt ảnh.

Hệ thống hoạt ảnh trong Flutter dựa trên các đối tượng Animation được định kiểu. Các widget có thể kết hợp các hoạt ảnh này trực tiếp trong các hàm xây dựng của chúng bằng cách đọc giá trị hiện tại của chúng và lắng nghe các thay đổi trạng thái của chúng hoặc chúng có thể sử dụng các hoạt ảnh làm cơ sở cho các hoạt ảnh phức tạp hơn mà chúng chuyển cho các widget khác.

Animation<double>

Trong Flutter, đối tượng Animation không hề biết gì về màn hình. Animation là một lớp trừu tượng hiểu giá trị hiện tại và trạng thái của nó (đã hoàn thành hoặc bị loại bỏ). Một trong những kiểu hoạt ảnh được sử dụng phổ biến hơn là Animation<double>.

Một đối tượng Animation tuần tự tạo ra các số nội suy giữa hai giá trị trong một khoảng thời gian nhất định. Đầu ra của đối tượng Animation có thể là tuyến tính, một đường cong, một hàm bước hoặc bất kỳ ánh xạ nào khác mà bạn có thể tạo ra. Tùy thuộc vào cách Animation được điều khiển, nó có thể chạy ngược lại hoặc thậm chí chuyển hướng ở giữa.

Hoạt ảnh cũng có thể nội suy các loại khác với double, chẳng hạn như Animation<Color> hoặc Animation<Size>.

Mỗi đối tượng Animation đều có trạng thái. Giá trị hiện tại của nó luôn có sẵn trong phương thức .value.

Animation không biết gì về kết xuất hoặc các hàm build().

CurvedAnimation

CurvedAnimation định nghĩa tiến trình của hoạt ảnh là một đường cong phi tuyến tính.

animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);

Lưu ý: Lớp Curves định nghĩa nhiều đường cong thường được sử dụng, hoặc bạn có thể tạo riêng của bạn. Ví dụ:

import 'dart:math';

class ShakeCurve extends Curve {
  @override
  double transform(double t) => sin(t * pi * 2);
}

Duyệt qua tài liệu Curves để có danh sách đầy đủ (với bản xem trước trực quan) về các hằng Curves đi kèm với Flutter.

CurvedAnimation và AnimationController (được mô tả trong phần tiếp theo) đều thuộc loại Animation<double>, vì vậy bạn có thể truyền chúng thay thế cho nhau. CurvedAnimation sẽ gộp đối tượng mà nó đang sửa đổi - bạn không phân lớp AnimationController để triển khai một đường cong.

AnimationController

AnimationController là một đối tượng Animation đặc biệt tạo ra một giá trị mới bất cứ khi nào phần cứng sẵn sàng cho một khung mới. Theo mặc định, một giá trị AnimationController tuyến tính tạo ra các số từ 0.0 đến 1.0 trong một khoảng thời gian nhất định. Ví dụ: đoạn code sau tạo một đối tượng Animation, nhưng không khởi động chạy nó:

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);

AnimationController bắt nguồn từ Animation<double>, vì vậy nó có thể được sử dụng ở bất cứ nơi nào cần đối tượng Animation. Tuy nhiên, AnimationController có các phương thức bổ sung để kiểm soát hoạt ảnh. Ví dụ: bạn bắt đầu một hoạt ảnh với phương thức .forward(). Việc tạo ra các số được gắn với việc làm mới màn hình, do đó, thông thường 60 số được tạo mỗi giây. Sau mỗi số được tạo, mỗi đối tượng Animation sẽ gọi các đối tượng Listener đính kèm. Để tạo danh sách hiển thị tùy chỉnh cho từng con, hãy xem RepaintBoundary.

Khi tạo một AnimationController, bạn truyền cho nó một đối số vsync. Sự hiện diện của vsync sẽ ngăn chặn các hoạt ảnh ngoài màn hình tiêu tốn tài nguyên không cần thiết. Bạn có thể sử dụng đối tượng trạng thái của mình làm vsync bằng cách thêm SingleTickerProviderStateMixin vào định nghĩa lớp. Bạn có thể xem một ví dụ về điều này trong animate1 trên GitHub.

Lưu ý: Trong một số trường hợp, một vị trí có thể vượt quá phạm vi 0.0-1.0 của AnimationController. Ví dụ, hàm fling() cho phép bạn cung cấp vận tốc, lực và vị trí (thông qua đối tượng Force). Vị trí có thể là bất kỳ thứ gì và vì vậy có thể nằm ngoài phạm vi 0.0-1.0.

CurvedAnimation cũng có thể vượt quá phạm vi 0.0-1.0, ngay cả khi AnimationController không vượt quá. Tùy thuộc vào đường cong được chọn, đầu ra của CurvedAnimation có thể có phạm vi rộng hơn đầu vào. Ví dụ: các đường cong đàn hồi chẳng hạn như Curves.elasticIn vượt quá mức đáng kể hoặc vượt quá phạm vi mặc định.

Tween

Theo mặc định, đối tượng AnimationController nằm trong khoảng từ 0.0 đến 1.0. Nếu bạn cần một phạm vi khác hoặc một kiểu dữ liệu khác, bạn có thể sử dụng một Tween để định cấu hình hoạt ảnh để nội suy cho một phạm vi hoặc kiểu dữ liệu khác. Ví dụ: giá trị Tween sau đây là từ -200.0 đến 0.0:

tween = Tween<double>(begin: -200, end: 0);

Tween là đối tượng không trạng thái chỉ nhận begin và end. Công việc duy nhất của Tween là định nghĩa một ánh xạ từ phạm vi đầu vào đến phạm vi đầu ra. Phạm vi đầu vào thường là 0.0 đến 1.0, nhưng đó không phải là một yêu cầu.

Tween thừa kế từ Animatable<T>, không phải từ Animation<T>Animatable giống Animation ở chô không cần phải xuất ra giá trị double. Ví dụ: ColorTween chỉ định sự tiến triển giữa hai màu.

colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween không lưu trữ bất kỳ trạng thái nào. Thay vào đó, nó cung cấp phương thức evaluate(Animation<double> animation) áp dụng chức năng ánh xạ cho giá trị hiện tại của hoạt ảnh. Giá trị hiện tại của Animation có thể được tìm thấy trong phương thức .value. Phương thức evaluate cũng thực hiện một số công việc quản lý, chẳng hạn như đảm bảo rằng phần bắt đầu và kết thúc được trả về khi giá trị hoạt ảnh tương ứng là 0.0 và 1.0.

Tween.animate

Để sử dụng đối tượng Tween thì ta gọi animate() vào Tween, đi qua trong đối tượng điều khiển. Ví dụ, đoạn mã sau tạo ra các giá trị nguyên từ 0 đến 255 trong khoảng thời gian 500ms.

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

Lưu ý: Phương thức animate() trả về một Animation, không phải là một Animatable.

Ví dụ sau cho thấy một controller, một đường cong và một Tween:

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
    CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

Thông báo hoạt ảnh

Đối tượng Animation có thể có nhiều Listener và nhiều StatusListener, được định nghĩa với các phương thức tương ứng là addListener() và addStatusListener()Listener được gọi bất cứ khi nào giá trị của hoạt ảnh thay đổi. Hành vi phổ biến nhất của Listener là gọi setState() để gây dựng lại. StatusListener được gọi khi một hoạt ảnh bắt đầu, kết thúc, di chuyển về phía trước hoặc di chuyển ngược lại, như được định nghĩa bởi AnimationStatus. Phần tiếp theo có một ví dụ về phương thức addListener() và Giám sát tiến trình của hoạt ảnh có một ví dụ về addStatusListener().

Ví dụ về hoạt ảnh

Phần này sẽ hướng dẫn bạn qua 5 ví dụ về hoạt ảnh. Mỗi phần cung cấp một liên kết đến mã nguồn cho ví dụ đó.

Kết xuất hoạt ảnh

Vấn đề ở đây là:

  • Cách thêm hoạt ảnh cơ bản vào tiện ích con bằng cách sử dụng addListener() và setState().
  • Mỗi khi Animation tạo ra một số mới thì phương thức addListener() sẽ gọi setState().
  • Cách định nghĩa một AnimationController với tham số vsync bắt buộc.
  • Hiểu cú pháp ".." trong "..addListener", còn được gọi là ký hiệu phân tầng của Dart.
  • Để đặt một lớp ở chế độ riêng tư, hãy bắt đầu tên của nó bằng dấu gạch dưới (_).

Cho đến thời điểm này thì bạn đã học được cách tạo một chuỗi số theo thời gian, nhưng không có gì được hiển thị (render) trên màn hình. Để hiển thị với một đối tượng Animation, hãy lưu trữ Animation như một thành viên của widget con của bạn, sau đó sử dụng giá trị của nó để quyết định cách vẽ.

Hãy xem ứng dụng sau đây vẽ biểu trưng Flutter mà không có hoạt ảnh:

import 'package:flutter/material.dart';

void main() => runApp(LogoApp());

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        height: 300,
        width: 300,
        child: FlutterLogo(),
      ),
    );
  }
}

Nguồn ứng dụng: animate0

Phần sau cho thấy cùng một đoạn mã đã được sửa đổi để tạo hiệu ứng hoạt hình cho logo phát triển từ kích thước 0 lên kích thước đầy đủ (full size). Khi định nghĩa một AnimationController, bạn phải truyền vào một đối tượng vsync. Tham số vsync được mô tả trong phần AnimationController.

Những thay đổi từ ví dụ không hoạt ảnh được đánh dấu:

{animate0 → animate1}/lib/main.dart
 
@@ -1,3 +1,4 @@
 
1
import 'package:flutter/animation.dart';
1
2
  import 'package:flutter/material.dart';
2
3
  void main() => runApp(LogoApp());
 
@@ -6,16 +7,39 @@
6
7
   _LogoAppState createState() => _LogoAppState();
7
8
  }
8
 
class _LogoAppState extends State<LogoApp> {
 
9
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
 
10
Animation<double> animation;
 
11
AnimationController controller;
 
12
+
 
13
@override
 
14
void initState() {
 
15
super.initState();
 
16
controller =
 
17
AnimationController(duration: const Duration(seconds: 2), vsync: this);
 
18
animation = Tween<double>(begin: 0, end: 300).animate(controller)
 
19
..addListener(() {
 
20
setState(() {
 
21
// The state that has changed here is the animation object’s value.
 
22
});
 
23
});
 
24
controller.forward();
 
25
}
 
26
+
9
27
   @override
10
28
   Widget build(BuildContext context) {
11
29
   return Center(
12
30
   child: Container(
13
31
   margin: EdgeInsets.symmetric(vertical: 10),
14
 
height: 300,
15
 
width: 300,
 
32
height: animation.value,
 
33
width: animation.value,
16
34
   child: FlutterLogo(),
17
35
   ),
18
36
   );
19
37
   }
 
38
+
 
39
@override
 
40
void dispose() {
 
41
controller.dispose();
 
42
super.dispose();
 
43
}
20
44
  }

Nguồn ứng dụng: animate1

Phương thức addListener() sẽ gọi setState(), vì vậy mỗi khi Animation tạo ra một số điện thoại mới, khung hiện tại sẽ được đánh dấu, và build() sẽ được gọi một lần nữa. Trong build(), vùng chứa thay đổi kích thước vì chiều cao và chiều rộng của nó hiện sử dụng animation.value thay vì giá trị được mã hóa cứng. Ta bỏ controller khi đối tượng State bị loại bỏ để tránh rò rỉ bộ nhớ.

Với một vài thay đổi này, bạn đã tạo hoạt ảnh đầu tiên của mình trong Flutter!

Thủ thuật ngôn ngữ của Dart: Bạn có thể không quen với ký hiệu phân tầng của Dart - hai dấu chấm trong ..addListener(). Cú pháp này có nghĩa là phương thức addListener() được gọi với giá trị trả về từ animate(). Hãy xem xét ví dụ sau:

animation = Tween<double>(begin: 0, end: 300).animate(controller)
  ..addListener(() {
    // ···
  });

Đoạn mã trên tương đương với:

animation = Tween<double>(begin: 0, end: 300).animate(controller);
animation.addListener(() {
    // ···
  });

Bạn có thể tìm hiểu thêm về ký hiệu tầng trong Tham quan Ngôn ngữ Dart.

Đơn giản hóa với AnimatedWidget

Vấn đề ở đây là:

  • Cách sử dụng lớp trợ giúp AnimatedWidget (thay vì addListener() và setState()) để tạo widget con hoạt ảnh.
  • Sử dụng AnimatedWidget để tạo một widget thực hiện hoạt ảnh có thể sử dụng lại. Để tách quá trình chuyển đổi khỏi widget, hãy sử dụng một AnimatedBuilder, như được thể hiện trong phần Cấu trúc lại bằng AnimatedBuilder.
  • Ví dụ về các AnimatedWidget trong API Flutter: AnimatedBuilderAnimatedModalBarrierDecoratedBoxTransitionFadeTransitionPositionedTransitionRelativePositionedTransitionRotationTransitionScaleTransitionSizeTransitionSlideTransition.

Lớp cơ sở AnimatedWidget cho phép bạn tách mã widget cốt lõi khỏi mã hoạt ảnh. AnimatedWidget không cần duy trì đối tượng State để giữ hoạt ảnh. Ta thêm lớp AnimatedLogo sau:

lib/main.dart (AnimatedLogo)
class AnimatedLogo extends AnimatedWidget {
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: FlutterLogo(),
      ),
    );
  }
}

AnimatedLogo sử dụng giá trị hiện tại của animation khi tự vẽ.

LogoApp vẫn quản lý AnimationController và Tween, và nó truyền đối tượng Animation tới AnimatedLogo:

{animate1 → animate2}/lib/main.dart
 
@@ -10,2 +27,2 @@
10
27
  class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
11
28
   Animation<double> animation;
 
@@ -13,32 +30,18 @@
13
30
   @override
14
31
   void initState() {
15
32
   super.initState();
16
33
   controller =
17
34
   AnimationController(duration: const Duration(seconds: 2), vsync: this);
18
 
animation = Tween<double>(begin: 0, end: 300).animate(controller)
 
35
animation = Tween<double>(begin: 0, end: 300).animate(controller);
19
 
..addListener(() {
20
 
setState(() {
21
 
// The state that has changed here is the animation object’s value.
22
 
});
23
 
});
24
36
   controller.forward();
25
37
   }
26
38
   @override
27
 
Widget build(BuildContext context) {
 
39
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
28
 
return Center(
29
 
child: Container(
30
 
margin: EdgeInsets.symmetric(vertical: 10),
31
 
height: animation.value,
32
 
width: animation.value,
33
 
child: FlutterLogo(),
34
 
),
35
 
);
36
 
}
37
40
   @override
38
41
   void dispose() {
39
42
   controller.dispose();
40
43
   super.dispose();
41
44
   }

Nguồn ứng dụng: animate2

Theo dõi tiến trình của hoạt ảnh

Vấn đề ở đây là:

  • Sử dụng addStatusListener() cho các thông báo về các thay đổi đối với trạng thái của hoạt ảnh, chẳng hạn như bắt đầu, dừng hoặc đảo ngược hướng.
  • Chạy hoạt ảnh trong một vòng lặp vô hạn bằng cách đảo ngược hướng khi hoạt ảnh đã hoàn thành hoặc trở lại trạng thái bắt đầu.

Thường sẽ hữu ích khi biết khi nào một hoạt ảnh thay đổi trạng thái, chẳng hạn như kết thúc, tiến lên hoặc đảo ngược. Bạn có thể nhận thông báo cho điều này với addStatusListener(). Đoạn mã sau sửa đổi ví dụ trên để nó lắng nghe sự thay đổi trạng thái và in bản cập nhật. Dòng được đánh dấu vàng thể hiện sự thay đổi:

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addStatusListener((state) => print('$state'));
    controller.forward();
  }
  // ...
}

Chạy đoạn mã trên ta được:

AnimationStatus.forward
AnimationStatus.completed

Tiếp theo, ta sử dụng addStatusListener() để đảo ngược hoạt ảnh ở đầu hoặc cuối. Điều này tạo ra hiệu ứng "thở":

{animate2 → animate3}/lib/main.dart
 
@@ -32,7 +32,15 @@
32
32
   void initState() {
33
33
   super.initState();
34
34
   controller =
35
35
   AnimationController(duration: const Duration(seconds: 2), vsync: this);
36
 
animation = Tween<double>(begin: 0, end: 300).animate(controller);
 
36
animation = Tween<double>(begin: 0, end: 300).animate(controller)
 
37
..addStatusListener((status) {
 
38
if (status == AnimationStatus.completed) {
 
39
controller.reverse();
 
40
} else if (status == AnimationStatus.dismissed) {
 
41
controller.forward();
 
42
}
 
43
})
 
44
..addStatusListener((state) => print('$state'));
37
45
   controller.forward();
38
46
   }

Nguồn ứng dụng: animate3

Cấu trúc lại bằng AnimatedBuilder

Vấn đề ở đây là:

  • AnimatedBuilder hiểu cách hiển thị quá trình chuyển đổi.
  • AnimatedBuilder không biết cách hiển thị widget, cũng như không quản lý đối tượng Animation.
  • Sử dụng AnimatedBuilder để mô tả hoạt ảnh như một phần của phương pháp xây dựng cho một tiện ích con khác. Nếu bạn chỉ muốn định nghĩa một tiện ích con có hoạt ảnh có thể sử dụng lại, hãy sử dụng AnimatedWidget, như được thể hiện trong phần Đơn giản hóa với AnimatedWidget.
  • Ví dụ về AnimatedBuilders trong API Flutter: BottomSheetExpansionTilePopupMenuProgressIndicatorRefreshIndicatorScaffoldSnackBarTabBarTextField.

Một vấn đề với code trong ví dụ animate3, đó là việc thay đổi hoạt ảnh bắt buộc phải thay đổi tiện ích hiển thị biểu trưng. Một giải pháp tốt hơn là tách các công việc thành các lớp khác nhau như sau:

  • Kết xuất logo
  • Định nghĩa đối tượng Animation
  • Kết xuất quá trình chuyển đổi

Bạn có thể thực hiện việc phân tách này với sự trợ giúp của lớp AnimatedBuilderAnimatedBuilder là một lớp riêng biệt trong cây kết xuất. Giống như AnimatedWidgetAnimatedBuilder sẽ tự động lắng nghe thông báo từ đối tượng Animation và đánh dấu cây widget đó nếu cần, vì vậy bạn không cần phải gọi addListener().

Cây widget cho ví dụ animate4 trông giống như sau:

Cây phụ tùng AnimatedBuilder

Bắt đầu từ cuối cây tiện ích con, đoạn code để hiển thị icon rất đơn giản như sau:

class LogoWidget extends StatelessWidget {
  // Leave out the height and width so it fills the animating parent
  Widget build(BuildContext context) => Container(
    margin: EdgeInsets.symmetric(vertical: 10),
    child: FlutterLogo(),
  );
}

Ba khối ở giữa trong sơ đồ đều được tạo theo phương thức build() trong GrowTransition, được hiển thị bên dưới. Bản thân widget GrowTransition là không trạng thái và chứa tập hợp các biến cuối cùng cần thiết để xác định hoạt ảnh chuyển tiếp. Hàm build() tạo và trả về AnimatedBuilder, lấy phương thức Anonymous ( builder) và đối tượng LogoWidget làm tham số. Công việc hiển thị quá trình chuyển đổi thực sự xảy ra trong phương thức Anonymous (builder), phương pháp này tạo ra một kích thước Container thích hợp để buộc LogoWidget phải thu nhỏ lại cho vừa vặn.

Một điểm khó khăn trong đoạn mã dưới đây là con sẽ trông giống như nó được chỉ định hai lần. Điều này xảy ra là vì tham chiếu bên ngoài của con được truyền tới AnimatedBuilder, tham chiếu này sẽ chuyển nó tới bao đóng ẩn danh, sau đó sử dụng đối tượng đó làm con của nó. Kết quả là AnimatedBuilder được chèn vào giữa hai widget trong cây kết xuất.

class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) => Center(
        child: AnimatedBuilder(
            animation: animation,
            builder: (context, child) => Container(
                  height: animation.value,
                  width: animation.value,
                  child: child,
                ),
            child: child),
      );
}

Cuối cùng, đoạn code để khởi tạo hoạt ảnh trông rất giống với ví dụ animate2. Phương thức initState() tạo ra một AnimationController và một Tween, sau đó liên kết chúng với animate(). Điều kỳ diệu xảy ra trong phương thức build(), phương thức này trả về một đối tượng GrowTransition với một LogoWidget như là con và một đối tượng hoạt ảnh để thúc đẩy quá trình chuyển đổi. Đây là ba yếu tố được liệt kê trong các gạch đầu dòng ở trên.

{animate2 → animate4}/lib/main.dart
 
@@ -27,22 +36,25 @@
27
36
  class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
28
37
   Animation<double> animation;
29
38
   AnimationController controller;
30
39
   @override
31
40
   void initState() {
32
41
   super.initState();
33
42
   controller =
34
43
   AnimationController(duration: const Duration(seconds: 2), vsync: this);
35
44
   animation = Tween<double>(begin: 0, end: 300).animate(controller);
36
45
   controller.forward();
37
46
   }
38
47
   @override
39
 
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
 
48
Widget build(BuildContext context) => GrowTransition(
 
49
child: LogoWidget(),
 
50
animation: animation,
 
51
);
40
52
   @override
41
53
   void dispose() {
42
54
   controller.dispose();
43
55
   super.dispose();
44
56
   }
45
57
  }

Nguồn ứng dụng: animate4

Hoạt ảnh đồng thời

Vấn đề ở đây là:

  • Lớp Curves định nghĩa một loạt các đường cong thường được sử dụng mà bạn có thể sử dụng với một CurvedAnimation.

Trong phần này, bạn sẽ xây dựng dựa trên ví dụ từ việc theo dõi tiến trình của hoạt ảnh (animate3 ), được sử dụng AnimatedWidget để tạo hoạt ảnh vào và ra liên tục. Hãy xem xét trường hợp bạn muốn tạo hoạt ảnh trong và ngoài trong khi độ mờ chuyển động từ trong suốt sang mờ đục.

 Lưu ý: Ví dụ này cho thấy cách sử dụng nhiều tween trên cùng một bộ điều khiển hoạt ảnh, trong đó mỗi tween quản lý một hiệu ứng khác nhau trong hoạt ảnh. Nó chỉ dành cho mục đích minh họa. Nếu bạn đang chỉnh sửa độ mờ và kích thước trong mã sản phẩm, bạn có thể sử dụng FadeTransition và SizeTransition thay thế.

Mỗi tween quản lý một khía cạnh của hoạt ảnh. Ví dụ:

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);
sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller);
opacityAnimation = Tween<double>(begin: 0.1, end: 1).animate(controller);

Bạn có thể lấy kích thước bằng sizeAnimation.value và độ mờ bằng opacityAnimation.value, nhưng hàm tạo AnimatedWidget chỉ lấy một đối tượng Animation duy nhất. Để giải quyết vấn đề này, ví dụ này sẽ tạo các đối tượng Tween của riêng nó và tính toán các giá trị một cách rõ ràng.

Thay đổi AnimatedLogo để đóng gói các đối tượng Tween của chính nó và phương thức build() của nó gọi Tween.evaluate() trên hoạt ảnh của cha nó để tính toán các giá trị kích thước và độ mờ cần thiết. Đoạn mã sau đây hiển thị các thay đổi với các phần highlight màu vàng:

class AnimatedLogo extends AnimatedWidget {
  // Make the Tweens static because they don't change.
  static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  static final _sizeTween = Tween<double>(begin: 0, end: 300);

  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: Container(
          margin: EdgeInsets.symmetric(vertical: 10),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: FlutterLogo(),
        ),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) => AnimatedLogo(animation: animation);

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

Nguồn ứng dụng: animate5

Bước tiếp theo

Bài hướng dẫn này cung cấp cho bạn nền tảng để tạo hoạt ảnh trong Flutter bằng cách sử dụng Tweens, nhưng có nhiều lớp khác để khám phá. Bạn có thể xem thêm các lớp Tween chuyên biệt, hoạt ảnh dành riêng cho Material Design, ReverseAnimation, chuyển đổi phần tử được chia sẻ (còn được gọi là hoạt ảnh Hero), mô phỏng vật lý và và phương thức fling(). Xem trang đích hoạt ảnh để biết các tài liệu và ví dụ mới nhất hiện có.

Nguồn: flutter.dev
» Tiếp: Hoạt ảnh ngầm
« Trước: Tổng quan về hoạt ảnh
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!