Tác giả Đoàn Ngọc Tú là giảng viên khóa Lộ trình Flutter
Thông tin chi tiết khóa học tại : https://flutter.techmaster.vn/
Bạn sẽ học được gì
- Cách sử dụng các class cơ bản từ thư viện Animation và thêm Animation vào 1 widget.
- Phân biệt khi nào sử dụng AnimatedWidget và AnimatedBuilder.
Bài viết chỉ cho bạn cách để tạo ra 1 explicit animation( explicit animation là gì mình sẽ giới thiệu sau) trong Flutter. Sau khi giới thiệu 1 vài khái niệm, class và method quan trọng, mình sẽ giúp các bạn hiểu rõ hơn thông qua một vài ví dụ cụ thể nhé! Các ví dụ này được xây dựng dựa trên nhau, từ đó sẽ giới thiệu cho các bạn các khía cạnh khác nhau của thư viện animation.
SDK của Flutter cũng cung cấp 1 bộ explicit animation được tích hợp sẵn, ví dụ: FadeTransition
, SizeTransition
, and SlideTransition
. Những animation đơn giản này được kích hoạt bằng cách cài đặt điểm bắt đầu và điểm kết thúc. Chúng dễ triển khai hơn những explicit animation tùy chỉnh, sẽ được mô tả cụ thể trong bài viết.
Các khái niệm và class về animation quan trọng
Vấn đề ở đây là gì?
Animation
, 1 class lõi trong thư viện animation của Flutter, thêm các giá trị để hướng dẫn 1 animation.- Một
Animation
object biết trạng thái hiện tại của animation (ví dụ, trạng thái có thể là đã bắt đầu, đã dừng lại hay đang diễn tiếp, hay đang trong trạng thái đảo ngược.), nhưng không biết bất cứ điều gì về những cái xuất hiện trên màn hình.- Một
AnimationController
quản lýAnimation
.- Một
CurvedAnimation
xác định tiến trình như 1 đường cong phi tuyến tính.- Một
Tween
xác định 1 mảng giá trị cho object đang được tạo animation. Ví dụ, 1Tween
có thể xác định 1 phép biến đổi từ màu đỏ sang màu xanh, hoặc giá trị từ 0 đến 255.- Sử dụng
Listeners
hoặcStatusListeners
để theo dõi trạng thái của animation.
Hệ thống animation trong Flutter dựa trên object Animation
. Widgets có thể kết hợp nhiều animation bằng cách đọc giá trị hiện tại của nó và lắng nghe sự thay đổi trạng thái, hoặc có thể sử dụng animation làm cơ sở cho các animation phức tạp hơn mà chúng sử dụng cho các widget khác.
Animation
Trong Flutter, một Animation
object không biết 1 điều gì về những cái đang hiển thị trên màn hình. Một Animation
là một abstract class có thể biết được giá trị và trạng thái hiện tại ( hoàn thành hay bị biến mất). Một trong những loại animation được sử dụng rộng rãi nhất.
Một Animation
object tạo ra 1 cách tuần tự các số có giá trị nằm giữa 2 giá trị trong 1 khoảng thời gian xác định. Đầu ra của một Animation
object có thể là 1 đường thẳng, 1 đường cong hoặc 1 chức năng theo bước, hoặc bất cứ ánh xạ nào khác mà bạn có thể nghĩ ra. Phụ thuộc vào cách Animation
object đó được kiểm soát, nó có thể chạy đảo ngược, hoặc thậm chí rẽ sang hướng khác ở đoạn giữa.
Animation có thể dùng nhiều loại dữ liệu khác double
, ví như Animation<Color>
hoặc Animation<Size>
.
Một Animation
object có trạng thái. Giá trị hiện tại của nó luôn luôn có sẵn trong field value
.
Một Animation
object không biết bất cứ gì về việc rendering hay hàm build()
.
CurvedAnimation
Một CurvedAnimation
xác định tiến trình của 1 animation như 1 đường cong phi tuyến tính.
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
Lưu ý: Class
Curves
định nghĩa nhiều đường cong thường được sử dụng hoặc bạn có thể tạo đường cong của riêng mình. Ví dụ:
import 'dart:math';
class ShakeCurve extends Curve {
@override
double transform(double t) => sin(t * pi * 2);
}
Bạn có thể xem tài liệu về Curve để có cái nhìn hoàn thiện về các hằng số của Curves
đi kèm trong 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ể chuyển chúng cho nhau. CurvedAnimation
bao bọc đối tượng mà nó đang sửa đổi — bạn đừng tạo lớp con của 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 hình mới. Theo mặc định, AnimationController
tạo tuyến tính 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 bắt đầu chạy:
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 đối tượng Animation
cần . Tuy nhiên, AnimationController
có các phương thức bổ sung để kiểm soát animation. Ví dụ: bạn bắt đầu một animation bằng 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 có 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 phần tử con, hãy xem RepaintBoundary.
Khi tạo một AnimationController
, bạn chuyển cho nó một đối số vsync
. Sự hiện diện của vsync
ngăn cản các animation 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 class.
Tween
Mặc định, AnimationController
object quy định giá trị nằm trong khoảng 0.0 đến 1.0. Nếu bạn cần 1 khoảng giá trị khác hoặc loại data khác, bạn có thể sử dụng Trên để cấu hình 1 animation và tạo ra 1 khoảng giá trị hoặc loại data khác. Ví dụ:
tween = Tween<double>(begin: -200, end: 0);
Tween
là 1 stateless object có khoảng begin
và end
. Việc duy nhất của Tween
là định nghĩa 1 ánh xạ từ khoảng giá trị input ra giá trị output. Giá trị input thường là 0.0 tới 1.0, nhưng là không bắt buộc.
Tween
kế thừa từ Animatable<T>
, không phải kế thừa từ Animation<T>
. Một Animatable
, giống như Animation
, nhưng không phải có giá trị đầu ra. Ví dụ ColorTween
xác định tiến trình biến đổi giữa 2 màu:
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);
Tween
object không lưu trữ state. Thay vào đó, nó cung cấp phương thức evaluate
giúp ánh xạ phương thức tới giá trị hiện tại của animation. Giá trị hiện tại của Animation
object có thể được tìm thấy trong phương thức .value
.
Tween.animate
Để sử dụng Tween
object, bạn có thể gọi method animate()
. Ví dụ sau đây tạo ra các giá trị từ 0 tới 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);
animate()
method trả vềAnimation
, không phải là mộtAnimatable
.
Ví dụ dưới đây tạo 1 controller
, 1 curve
, và 1 Tween
:
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> curve =
CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
Animation notifications
Một Animation
object có thể có Listeners
và StatusListeners
, được định nghĩa bằng addListener()
và addStatusListener()
. Một Listener
được gọi bất cứ khi nào giá trị của Animation
thay đổi. Hành vi phổ biến nhất của một Listener là gọi setState () để tạo lại giao diện. StatusListener
được gọi khi một Animation
bắt đầu, kết thúc, di chuyển về phía trước hoặc di chuyển ngược lại, giống như được định nghĩa bởi AnimationStatus
. Phần tiếp theo của bài viết sẽ có một ví dụ về phương thức addListener()
và theo dõi tiến trình của Animation
sẽ chỉ ra một ví dụ về addStatusListener()
.
Các ví dụ về Animation
Lý thuyết dài dòng và hơi khó hiểu đúng không? Vậy thì phần tiếp theo sau đây chúng ta sẽ đến với các ví dụ thực tế về animation để hiểu rõ hơn nhé. Mỗi ví dụ mình sẽ demo các đoạn code cũng như link tới source code để các bạn có thể tham khảo dễ dàng hơn.
Rendering animations
Phần này chúng ta học được gì?
- Cách tạo 1 animation đơn giản và thêm nó vào trong widget sử dụng
addListener()
vàsetState()
.- Mỗi khi
Animation
tạo 1 số mới,addListener()
function sẽ gọisetState()
.- Cách để định nghĩa 1
AnimationController
với paramvsync
.- Hiểu được
..
syntax trong..addListener
, còn được biết đến dưới cách gọiDart’s cascade notation
.- Để tạo 1 class private, bắt đầu tên nó với tiền tố
_
.
Cho đến lúc này, bạn đã học được cách tạo ra một dãy số trong 1 khoảng thời gian. Không gì được render vào màn hình. Để render với 1 Animation
object, lưu trữ Animation
object như là 1 thành phần của widget của bạn, sau đó sử dụng giá trị của nó để quyết định khi nào vẽ lên trên màn hình.
Ví dụ dưới đây sẽ vẽ Flutter logo mà không có animation:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: 300,
width: 300,
child: const FlutterLogo(),
),
);
}
}
App source animate()
Đoạn code tiếp theo sử dụng animation để tạo logo có size từ 0 đến full sim. Khi định nghĩa 1 AnimationController
, bạn phải truyền vsync
object. Chúng ta sẽ nói cụ thể hơn về vsync
trong phần tới.
@@ -9,16 +9,39 @@
99 _LogoAppState createState() => _LogoAppState();
10 }
11 - class _LogoAppState extends State<LogoApp> {
11 + class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
12 + late Animation<double> animation;
13 + late AnimationController controller;
14 +
15 + @override
16 + void initState() {
17 + super.initState();
18 + controller =
19 + AnimationController(duration: const Duration(seconds: 2), vsync: this);
20 + animation = Tween<double>(begin: 0, end: 300).animate(controller)
21 + ..addListener(() {
22 + setState(() {
23 + // The state that has changed here is the animation object’s value.
24 + });
25 + });
26 + controller.forward();
27 + }
28 +
29 @override
30 Widget build(BuildContext context) {
31 return Center(
32 child: Container(
33 margin: const EdgeInsets.symmetric(vertical: 10),
17 - height: 300,
18 - width: 300,
34 + height: animation.value,
35 + width: animation.value,
36 child: const FlutterLogo(),
37 ),
38 );
39 }
40 +
41 + @override
42 + void dispose() {
43 + controller.dispose();
44 + super.dispose();
45 + }
46 }
App source animate1
Phương thức addListener()
sẽ gọi setState()
, nên mỗi lần Animation
tạo ra 1 số mới, Frame hiện tại sẽ được đánh dấu là đã cũ và sẽ buộc build()
được gọi lại. Trong build()
, container thay đổi size bởi vì chiều cao và rộng của nó sử dụng animation.value
thay vì size được fix cứng. Chú ý dispose controller khi state object bị hủy để tránh Memory leak nhé.
Như vậy, với 1 vài thay đổi, bạn đã tạo ra animation đầu tiên trong Flutter rồi đó.
Chú ý: trong ví dụ trên có sử dụng
Dart’s cascade notation
:..addListener()
:
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
// ···
});
tương đương đoạn code sau:
animation = Tween<double>(begin: 0, end: 300).animate(controller);
animation.addListener(() {
// ···
});
Simplifying with AnimatedWidget - Đơn giản hóa animation với AnimatedWidget
Ở phần này chúng ta sẽ học được gì:
- Cách sử dụng
AnimatedWidget
class ( thay vì sử dụngaddListener()
vàsetState()
) để tạo 1 widget có thể animate.- Sử dụng
AnimatedWidget
để tạo widget chạy 1 animation có thể tái sử dụng. Để phân tách sự dịch chuyển từ widget, sử dụngAnimatedBuilder
, sẽ được đề cập tới ở phần sau.- Các ví dụ về các
AnimatedWidget
trong Flutter API:AnimatedBuilder, AnimatedModalBarrier, DecoratedBoxTransition, FadeTransition, PositionedTransition, RelativePositionedTransition, RotationTransition, ScaleTransition, SizeTransition, SlideTransition
.
Ví dụ số 1: AnimatedLogo
:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
AnimatedLogo
sử dụng giá trị hiện tại của animation
khi tự vẽ chính nó.
LogoApp
vẫn sẽ quản lý AnimationController
và Tween
và truyền Animation
object vào trong AnimatedLogo
. Chúng ta sẽ có đoạn code như sau:
Ví dụ số 2:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
// #docregion AnimatedLogo
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
// #enddocregion AnimatedLogo
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late 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);
controller.forward();
}
@override
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
Monitoring the progress of the animation
Những gì sẽ nắm được:
- Sử dụng
addStatusListener()
cho thông báo thay đổi trạng thái của animation, như là bắt đầu, dừng lại hay đổi chiều animation.- Chạy 1 animation lặp lại bằng cách đảo ngược animation khi animation chạy xong hoặc trở lại trạng thái ban đầu.
Việc biết khi nào trạng thái animation thay đổi( kết thúc, bắt đầu, đang chạy, quay ngược lại) là rất cần thiết. Bạn có thể nhận thông báo cho việc này bằng cách sử dụng addStatusListener()
. Đoạn code sau chỉnh sửa đôi chút ví dụ trước đó để có thể lắng nghe thay đổi.
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late 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((status) => print('$status'));
controller.forward();
}
// ...
}
Output của đoạn code trên:
AnimationStatus.forward
AnimationStatus.completed
Tiếp theo, chúng ta sẽ sử dụng addStatusListener()
để đảo ngược animation lúc bắt đầu hoặc lúc kết thúc
Ví dụ số 3:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
// #docregion print-state
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late 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)
// #enddocregion print-state
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
})
// #docregion print-state
..addStatusListener((status) => print('$status'));
controller.forward();
}
// #enddocregion print-state
@override
Widget build(BuildContext context) => AnimatedLogo(animation: animation);
@override
void dispose() {
controller.dispose();
super.dispose();
}
// #docregion print-state
}
Refactoring with AnimatedBuilder
Chúng ta sẽ học được gì?
Một
AnimatedBuilder
hiểu được cách hiển thị quá trình chuyển đổi.
AnimatedBuilder
không biết cách hiển thị widget con, cũng như không quản lý đối tượngAnimation
.Sử dụng
AnimatedBuilder
để mô tả animation như 1 phần củabuild()
cho widget khác.Nếu đơn giản bạn muốn xác định 1 widget với 1 animation có khả năng tái sử dụng, bạn có thể dùng
AnimatedWidget
( chúng ta đã nói tới ở phần trước đó)Một số ví dụ về
AnimatedBuilder
trong flutter:BottomSheet, ExpansionTile, PopupMenu, ProgressIndicator, RefreshIndicator, Scaffold, SnackBar, TabBar, TextField
.
Có một vấn đề với đoạn code ở ví dụ số 3, đó là thay đổi animation yêu cầu theo việc thay đổi widget tạo ra logo. Một giải pháp tốt hơn đó là phân tách trách nhiệm cho từng class khác nhau:
Class tạo logo.
Class xác định
Animation
object.Class tạo Quá trình chuyển đổi.
Bạn có thể đạt được việc phân tách này với sự giúp đỡ từ class
AnimatedBuilder
. MộtAnimatedBuilder
là 1 class trong render trê. Giống nhưAnimatedWidget
,AnimatedBuilder
tự động lắng nghe thông báo từAnimation
object, và đánh dấu widget cần render lại, do vậy bạn không cần gọiaddListener()
Widget tree cho ví dụ số 4 này như sau:
Bắt đầu từ dưới lên theo widget tree trên, code cho logo như sau:
class LogoWidget extends StatelessWidget {
const LogoWidget({Key? key}) : super(key: key);
// Leave out the height and width so it fills the animating parent
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
Ba khối ở giữa trong sơ đồ đều được tạo trong phương thức build () trong GrowTransition
, được hiển thị bên dưới. Bản thân widget GrowTransition
là stateless và chứa tập hợp các biến cuối cùng cần thiết để xác định chuyển tiếp của animation. Hàm build()
tạo và trả về AnimatedBuilder, hàm này nhận 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 một Container
chứa có kích thước 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à child trông giống như nó được chỉ định hai lần. Điều đang xảy ra là tham chiếu bên ngoài của child được chuyển đến AnimatedBuilder
, tham chiếu này sẽ chuyển nó tới vùng ẩn danh, sau đó sử dụng đối tượng đó làm con của nó. Kết quả thực là AnimatedBuilder
được chèn vào giữa hai widget trong widget tree.
class GrowTransition extends StatelessWidget {
const GrowTransition({required this.child, required this.animation, Key? key})
: super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
Cuối cùng, đoạn code tạo animation ở ví dụ này khá giống với ví dụ số 2. initState()
tạo 1 AnimationController
và 1 Tween
, sau đó bind chúng với phương thức animate()
. Source code đầy đủ như sau:
Ví dụ số 4:
import 'package:flutter/material.dart';
void main() => runApp(const LogoApp());
// #docregion LogoWidget
class LogoWidget extends StatelessWidget {
const LogoWidget({Key? key}) : super(key: key);
// Leave out the height and width so it fills the animating parent
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
// #enddocregion LogoWidget
// #docregion GrowTransition
class GrowTransition extends StatelessWidget {
const GrowTransition({required this.child, required this.animation, Key? key})
: super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
// #enddocregion GrowTransition
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
// #docregion print-state
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late 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);
controller.forward();
}
// #enddocregion print-state
@override
Widget build(BuildContext context) {
return GrowTransition(
child: const LogoWidget(),
animation: animation,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
// #docregion print-state
}
Simultaneous animations
Trong phần này, bạn sẽ tạo 1 ví dụ từ việc theo dõi tiến trình của animation từ ví dụ số 3, ví dụ sử dụng AnimatedWidget
để tạo animation phóng to và thu nhỏ liên tục. Hãy xem xét trường hợp bạn muốn tạo animation phóng to và thu nhỏ trong khi độ mờ của animation từ trong suốt sang mờ đục.
Ví dụ dưới đây sẽ hướng dẫn bạn cách kết hợp nhiều với cùng 1
AnimationController
, mỗi một tween quản lý 1 hiệu ứng khác nhau cho animation. Nếu bạn sử dụng animation liên quan tới size và opacity, bạn có thể sử dụngFadeTransition
vảSizeTransition
thay thế.Mỗi tween quản lý 1 phần của animation. 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 sẽ lấy được kích cỡ từ sizeAnimation.value
và lấy được độ mờ từ sizeAnimation.value
.
Code cho ví dụ số 5:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({Key? key, required Animation<double> animation})
: super(key: key, listenable: animation);
// 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);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: const FlutterLogo(),
),
),
);
}
}
class LogoApp extends StatefulWidget {
const LogoApp({Key? key}) : super(key: key);
@override
_LogoAppState createState() => _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
late Animation<double> animation;
late 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();
}
}
Tổng kết
Hướng dẫn này cung cấp cho bạn nền tảng để tạo animation trong Flutter bằng Tweens
, nhưng có nhiều class khác để khám phá. Bạn có thể tìm hiểu thêm các lớp Tween
, animation chuyên biệt dành riêng cho Material Design, ReverseAnimation
, chuyển đổi phần tử được chia sẻ (còn được gọi là Hero animation), mô phỏng vật lý và phương thức fling()
. Bạn có thể xem thêm tại đây cho các tài liệu và ví dụ mới nhất hiện có.
Tham khảo
Tham khảo them https://docs.flutter.dev/development/ui/widgets/animation
Bình luận