Flutter: Thêm tương tác vào ứng dụng Flutter
Giải phóng thời gian, khai phóng năng lực
Mục lục bài viết
- Các widget trạng thái và không trạng thái
- Tạo một widget trạng thái
- Quản lý trạng thái
- Các widget tương tác khác
- Tài nguyên
Trong bài viết này bạn sẽ tìm hiểu các phần chính:
- Làm thế nào để hồi đáp các lần nhấn chuột.
- Cách tạo một widget tùy chỉnh.
- Sự khác biệt giữa widget trạng thái và không trạng thái.
Làm cách nào để bạn sửa đổi ứng dụng của mình để làm cho ứng dụng phản ứng với đầu vào của người dùng? Trong bài viết này, bạn sẽ thêm tính tương tác vào một ứng dụng chỉ chứa các widget không tương tác. Cụ thể, bạn sẽ sửa đổi một biểu tượng để làm cho nó có thể sử dụng được bằng cách tạo một widget trạng thái tùy chỉnh để quản lý hai widget không trạng thái.
Bài viết Hướng dẫn xây dựng bố cục cho bạn cách tạo bố trí cho các ảnh chụp màn hình sau đây:
Ứng dụng hướng dẫn bố cục
Khi ứng dụng lần đầu tiên khởi chạy, ngôi sao có màu đỏ đặc, cho thấy rằng hồ trên đã được yêu thích trước đó. Con số bên cạnh ngôi sao chỉ ra rằng 41 người đã yêu thích hồ này. Sau khi hoàn thành hướng dẫn này, việc chạm vào ngôi sao sẽ xóa trạng thái ưa thích của nó, thay thế ngôi sao đặc bằng một đường viền và giảm số lượng. Chạm lại vào hồ yêu thích thì lại vẽ lại một ngôi sao đặc và tăng số lượng.
Để thực hiện điều này, bạn sẽ tạo một widget tùy chỉnh duy nhất bao gồm cả dấu sao và số lượng, và chính chúng là các widget con. Nhấn vào dấu sao sẽ thay đổi trạng thái cho cả hai widget con, vì vậy, cùng một widget con sẽ quản lý cả hai.
Bạn có thể chạm vào mã ngay trong Bước 2: Phân lớp StatefulWidget. Nếu bạn muốn thử các cách quản lý trạng thái khác nhau, hãy chuyển đến Quản lý trạng thái.
Các widget trạng thái và không trạng thái
Một widget có thể là có trạng thái hoặc không trạng thái. Nếu một tiện ích con có thể thay đổi - ví dụ như khi người dùng tương tác với nó - thì nó ở trạng thái.
Một widget không trạng thái không bao giờ thay đổi. Icon, IconButton và Text là ví dụ về các widget không trạng thái. Lớp con vật dụng không trạng thái StatelessWidget.
Widget stateful là động: ví dụ, nó có thể thay đổi diện mạo của mình để đáp ứng với các sự kiện kích hoạt bằng cách tương tác người dùng hoặc khi nó nhận được dữ liệu. Checkbox, Radio, Slider, InkWell, Form, và TextField là ví dụ về các widget stateful. Lớp con của các widget trạng thái là StatefulWidget.
Trạng thái của widget con được lưu trữ trong đối tượng State, tách biệt trạng thái của tiện ích ra khỏi hình thức của nó. Trạng thái bao gồm các giá trị có thể thay đổi, như giá trị hiện tại của thanh trượt hoặc hộp kiểm có được chọn hay không. Khi trạng thái của widget thay đổi, thì đối tượng trạng thái sẽ gọi setState(), yêu cầu khung vẽ lại widget.
Tạo một widget trạng thái
Vấn đề ở đây là gì?
- Một widget trạng thái được thực thi bởi hai lớp: lớp con của
StatefulWidget
và lớp con củaState
. - Lớp trạng thái chứa trạng thái có thể thay đổi của widget con và phương thức
build()
của widget con. - Khi trạng thái của widget thay đổi, đối tượng trạng thái sẽ gọi
setState()
, yêu cầu framework vẽ lại widget.
Trong phần này, bạn sẽ tạo một widget trạng thái tùy chỉnh. Bạn sẽ thay thế hai widget không trạng thái - dấu sao màu đỏ liền mạch và số đếm bên cạnh - bằng một widget trạng thái tùy chỉnh duy nhất quản lý một hàng có hai widget con: IconButton
và Text
.
Việc triển khai một widget trạng thái tùy chỉnh yêu cầu tạo hai lớp:
- Một lớp con của
StatefulWidget
để định nghĩa widget. - Một lớp con của
State
để chứa trạng thái của widget đó và định nghĩa phương thứcbuild()
của widget .
Phần này chỉ cho bạn cách tạo một widget trạng thái được gọi là FavoriteWidget
dùng cho app hồ ở trên. Sau khi thiết lập, bước đầu tiên của bạn là chọn cách quản lý trạng thái FavoriteWidget
.
Bước 0: Chuẩn bị sẵn sàng
Nếu bạn đã xây dựng app trong hướng dẫn bố cục tòa nhà (bước 6), hãy chuyển sang phần tiếp theo.
- Đảm bảo rằng bạn đã thiết lập môi trường của mình.
- Tạo ứng dụng Flutter “Hello World” cơ bản.
- Thay thế nội dung file
lib/main.dart
bằng main.dart. - Thay thế nội dung file
pubspec.yaml
bằng pubspec.yaml. - Tạo một thư mục tên
images
trong dự án của bạn và thêm ảnh lake.jpg.
Sau khi bạn có thiết bị được kết nối và kích hoạt hoặc bạn đã khởi chạy trình mô phỏng iOS (một phần của cài đặt Flutter) hoặc trình mô phỏng Android (một phần của cài đặt Android Studio), bạn đã sẵn sàng!
Bước 1: Quyết định đối tượng nào quản lý trạng thái của widget
Trạng thái của widget con có thể được quản lý theo nhiều cách, nhưng trong ví dụ của ta, chính widget con FavoriteWidget
sẽ quản lý trạng thái của chính nó. Trong ví dụ này, chuyển đổi dấu sao là một hành động riêng biệt không ảnh hưởng đến widget con chính hoặc phần còn lại của giao diện người dùng, vì vậy widget con có thể xử lý trạng thái nội bộ của nó.
Tìm hiểu thêm về cách tách widget con và trạng thái cũng như cách trạng thái có thể được quản lý, trong Trạng thái quản lý.
Bước 2: Phân lớp StatefulWidget
Lớp FavoriteWidget
sẽ quản lý trạng thái riêng của mình, vì vậy nó sẽ ghi đè createState()
để tạo ra một đối tượng State
. Framework sẽ gọi createState()
khi nó muốn xây dựng widget con. Trong ví dụ này, createState()
sẽ trả về một đối tượng _FavoriteWidgetState
và bạn sẽ triển khai trong bước tiếp theo.
class FavoriteWidget extends StatefulWidget { @override _FavoriteWidgetState createState() => _FavoriteWidgetState(); }
Lưu ý: Các thành viên hoặc lớp bắt đầu bằng dấu gạch dưới (
_
) là riêng tư. Để biết thêm thông tin, hãy xem Thư viện và khả năng hiển thị, một phần trong chuyến tham quan ngôn ngữ Dart.
Bước 3: Trạng thái lớp con
Lớp _FavoriteWidgetState
lưu trữ các dữ liệu mà có thể thay đổi theo thời gian tồn tại của các widget. Khi ứng dụng lần đầu tiên khởi chạy, giao diện người dùng sẽ hiển thị một ngôi sao màu đỏ đặc, cho biết rằng hồ có trạng thái "yêu thích", cùng với 41 lượt thích. Các giá trị này được lưu trữ trong các trường _isFavorited
và _favoriteCount
:
class _FavoriteWidgetState extends State<FavoriteWidget> { bool _isFavorited = true; int _favoriteCount = 41; // ··· }
Lớp cũng định nghĩa phương thức build()
, phương thức này tạo ra một hàng có chứa IconButton
màu đỏ và Text
. Bạn sử dụng IconButton (thay vì Icon
) vì nó có thuộc tính onPressed
để định nghĩa hàm callback (_toggleFavorite
) để xử lý một lần nhấn. Tiếp theo, bạn sẽ định nghĩa hàm callback như sau.
class _FavoriteWidgetState extends State<FavoriteWidget> { // ··· @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.all(0), child: IconButton( padding: EdgeInsets.all(0), alignment: Alignment.centerRight, icon: (_isFavorited ? Icon(Icons.star) : Icon(Icons.star_border)), color: Colors.red[500], onPressed: _toggleFavorite, ), ), SizedBox( width: 18, child: Container( child: Text('$_favoriteCount'), ), ), ], ); } }
Mẹo: Việc đặt
Text
trong một SizedBox và thiết lập chiều rộng của nó sẽ ngăn cản một "bước nhảy" có thể nhận thấy khi văn bản thay đổi giữa các giá trị 40 và 41 - nếu không sẽ xảy ra một bước nhảy vì các giá trị đó có chiều rộng khác nhau.
Phương thức _toggleFavorite()
, được gọi khi IconButton
được nhấn, khi đó nó sẽ gọi setState()
. Việc gọi setState()
là rất quan trọng, vì điều này sẽ cho framework biết rằng trạng thái của widget con đã thay đổi và widget con phải được vẽ lại. Đối số hàm có tác dụng để setState()
chuyển đổi giao diện người dùng giữa hai trạng thái này:
- Một biểu tượng
star
và số 41 - Một biểu tượng
star_border
và số 40
void _toggleFavorite() { setState(() { if (_isFavorited) { _favoriteCount -= 1; _isFavorited = false; } else { _favoriteCount += 1; _isFavorited = true; } }); }
Bước 4: Đưa (plug) widget trạng thái vào cây tiện ích
Thêm widget trạng thái tùy chỉnh của bạn vào cây widget trong phương thức build()
của app. Trước tiên ta cần xác định vị trí đoạn mã tạo Icon
và Text
và xóa nó. Ở cùng một vị trí, hãy tạo widget con trạng thái:
Hoàn thành! Khi bạn tải lại nóng ứng dụng, biểu tượng dấu sao bây giờ sẽ phản hồi với các lần nhấn.
Các vấn đề?
Nếu bạn không thể chạy mã của mình, hãy xem IDE của bạn để biết các lỗi có thể xảy ra. Gỡ lỗi ứng dụng Flutter có thể hữu ích. Nếu bạn vẫn không thể tìm thấy sự cố, hãy kiểm tra mã của bạn với ví dụ về hồ tương tác trên GitHub.
Nếu bạn vẫn còn thắc mắc, hãy tham khảo bất kỳ kênh nào trong số các kênh cộng đồng nhà phát triển.
Phần tiếp theo của bài viết này trình bày một số cách có thể quản lý trạng thái của widget con và liệt kê các widget con tương tác có sẵn khác.
Quản lý trạng thái
Vấn đề ở đây là gì?
- Có nhiều cách tiếp cận khác nhau để quản lý trạng thái.
- Bạn, với tư cách là người thiết kế widget, hãy chọn cách tiếp cận để sử dụng.
- Nếu nghi ngờ, hãy bắt đầu bằng cách quản lý trạng thái trong widget con.
Ai quản lý trạng thái của tiện ích con stateful? Chính các widget đó? Các widget con? Cả hai? Đối tượng khác? Câu trả lời còn phụ thuộc vào nhiều thứ. Có một số cách hợp lệ để làm cho widget con của bạn tương tác. Bạn, với tư cách là người thiết kế widget, đưa ra quyết định dựa trên cách bạn mong đợi widget của mình sẽ được sử dụng. Dưới đây là những cách phổ biến nhất để quản lý trạng thái:
- Widget tự quản lý trạng thái của nó
- Widget cha quản lý trạng thái của widget con
- Kết hợp các phương pháp
Làm thế nào để bạn quyết định cách tiếp cận để sử dụng? Các nguyên tắc sau sẽ giúp bạn quyết định:
- Nếu trạng thái được đề cập là dữ liệu người dùng, ví dụ như chế độ được chọn hoặc không được chọn của hộp kiểm hoặc vị trí của thanh trượt, thì trạng thái được quản lý tốt nhất bởi widget con.
- Nếu trạng thái được đề cập liên quan đến mỹ thuật, chẳng hạn như một hình ảnh động, thì trạng thái đó được quản lý tốt nhất bởi chính widget.
Bạn hãy bắt đầu bằng cách quản lý trạng thái trong widget con xem sao.
Ta sẽ đưa ra các ví dụ về các cách quản lý trạng thái khác nhau bằng cách tạo ba ví dụ đơn giản: TapboxA, TapboxB và TapboxC. Tất cả các ví dụ đều hoạt động tương tự - mỗi ví dụ tạo ra một container mà khi chạm vào sẽ chuyển đổi giữa hộp màu xanh lục hoặc màu xám. Giá trị boolean của _active
sẽ xác định màu: màu xanh lá cây là active hoặc màu xám là inactive.
Những ví dụ trên sử dụng GestureDetector để nắm bắt hoạt động trên Container
.
Widget tự quản lý trạng thái của nó
Đôi khi, việc widget con quản lý trạng thái nội bộ là điều hợp lý nhất. Ví dụ: ListView tự động cuộn khi nội dung của nó vượt quá hộp kết xuất. Hầu hết các nhà phát triển sử dụng ListView
không muốn quản lý hành vi cuộn của ListView
, vì vậy ListView
chính nó sẽ quản lý độ lệch cuộn của nó.
Lớp _TapboxAState
:
- Quản lý trạng thái cho
TapboxA
. - Định nghĩa
_active
để xác định màu hiện tại của hộp. - Định nghĩa hàm
_handleTap()
sẽ cập nhật_active
khi chạm vào hộp và gọi hàmsetState()
để cập nhật giao diện người dùng. - Triển khai tất cả các hành vi tương tác cho widget con.
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
Widget cha quản lý trạng thái của widget con
Thường thì widget cha có ý nghĩa nhất trong việc quản lý trạng thái và thông báo cho widget con của nó khi nào cần cập nhật. Ví dụ: IconButton cho phép bạn coi một biểu tượng như một nút có thể nhấn. IconButton
là một widget con không trạng thái vì tiện ích con cần biết liệu nút đã được nhấn hay chưa để có thể thực hiện hành động thích hợp.
Trong ví dụ sau, TapboxB xuất trạng thái của nó sang trạng thái cha của nó thông qua một lệnh callback. Vì TapboxB không quản lý bất kỳ trạng thái nào nên nó phân lớp StatelessWidget.
Lớp ParentWidgetState:
- Quản lý trạng thái
_active
cho TapboxB. - Triển khai
_handleTapboxChanged()
, phương thức được gọi khi hộp được chạm vào. - Khi trạng thái thay đổi, lời gọi
setState()
sẽ cập nhật giao diện người dùng.
Lớp TapboxB:
- Mở rộng StatelessWidget vì tất cả trạng thái được xử lý bởi cha của nó.
- Khi được nhấn, nó sẽ thông báo cho cha của nó.
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxB(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
TapboxB({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
Mẹo: Khi tạo API, hãy cân nhắc sử dụng
@required
cho bất kỳ thông số nào mà mã của bạn dựa vào. Để sử dụng@required
, hãy đưa vào thư việnfoundation
(xuất lại thư việnmeta.dart
của Dart):
import 'package:flutter/foundation.dart';
Kết hợp các phương pháp
Đối với một số widget, cách tiếp cận kết hợp các phương pháp có ý nghĩa nhất. Trong trường hợp này, widget trạng thái quản lý một số trạng thái và widget cha quản lý các khía cạnh khác của trạng thái.
Trong ví dụ TapboxC
, khi nhấn chuột thì một đường viền màu xanh lá cây đậm sẽ xuất hiện xung quanh hộp. Khi nhả chuột thì đường viền biến mất và màu của hộp thay đổi. TapboxC
xuất trạng thái _active
của nó cho cha của nó nhưng quản lý trạng thái nội bộ _highlight
của nó. Ví dụ này có hai đối tượng State
là _ParentWidgetState
và _TapboxCState
.
Đối tượng _ParentWidgetState
:
- Quản lý trạng thái
_active
. - Triển khai
_handleTapboxChanged()
, phương thức được gọi khi hộp được chạm vào. - Lời gọi
setState()
để cập nhật giao diện người dùng khi một lần nhấn xảy ra và trạng thái_active
thay đổi.
Đối tượng _TapboxCState
:
- Quản lý trạng thái
_highlight
. GestureDetector
lắng nghe tất cả các sự kiện nhấn. Khi người dùng nhấn xuống, nó sẽ thêm phần highlight (được thực hiện dưới dạng đường viền màu xanh lá cây đậm). Khi người dùng nhả nút, nó sẽ xóa phần highlight.- Lời gọi
setState()
để cập nhật giao diện người dùng khi nhấn xuống, nhấn lên hoặc nhấn hủy và trạng thái_highlight
sẽ thay đổi. - Sự kiện nhấn sẽ truyền thay đổi trạng thái đó cho widget con để thực hiện hành động thích hợp bằng cách sử dụng thuộc tính widget.
//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return Container(
child: TapboxC(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
TapboxC({Key key, this.active: false, @required this.onChanged})
: super(key: key);
final bool active;
final ValueChanged<bool> onChanged;
_TapboxCState createState() => _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: Container(
child: Center(
child: Text(widget.active ? 'Active' : 'Inactive',
style: TextStyle(fontSize: 32.0, color: Colors.white)),
),
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color:
widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
Một triển khai thay thế có thể đã xuất trạng thái đánh dấu sang trạng thái chính trong khi vẫn giữ trạng thái hoạt động bên trong, nhưng nếu bạn yêu cầu ai đó sử dụng hộp nhấn đó, họ có thể sẽ phàn nàn rằng điều đó không có ý nghĩa gì. Nhà phát triển quan tâm xem hộp có hoạt động hay không. Nhà phát triển có thể không quan tâm đến cách quản lý phần đánh dấu và muốn hộp nhấn xử lý các chi tiết đó.
Các widget tương tác khác
Flutter cung cấp nhiều loại nút và các widget tương tác tương tự. Hầu hết các widget con này đều triển khai các nguyên tắc Material Design, nguyên tắc này định nghĩa một tập hợp các thành phần với một giao diện người dùng phù hợp.
Nếu muốn, bạn có thể sử dụng GestureDetector để xây dựng tính tương tác vào bất kỳ widget con tùy chỉnh nào. Bạn có thể tìm thấy ví dụ về GestureDetector
trong Quản lý trạng thái. Tìm hiểu thêm về GestureDetector
trong Handle tap , một công thức trong cookbook Flutter.
Mẹo: Flutter cũng cung cấp một tập hợp các widget kiểu iOS được gọi là Cupertino.
Khi bạn cần sự tương tác, cách dễ nhất là sử dụng một trong những widget có sẵn. Đây là danh sách một số widget có sẵn:
Widget tiêu chuẩn
Thành phần Material
- Checkbox
- DropdownButton
- TextButton
- FloatingActionButton
- IconButton
- Radio
- ElevatedButton
- Slider
- Switch
- TextField
Tài nguyên
Các tài nguyên sau có thể hữu ích khi thêm tính tương tác vào ứng dụng của bạn.
Cử chỉ, một phần trong cookbook Flutter.
Xử lý cử chỉ, một phần trong Giới thiệu về widget con: Cách tạo một nút và làm cho nó phản hồi với đầu vào.
Cử chỉ trong Flutter: Mô tả cơ chế cử chỉ của Flutter.
Tài liệu API Flutter: Tài liệu tham khảo cho tất cả các thư viện Flutter.
Ứng dụng đang chạy Flutter Gallery, repo: Ứng dụng demo giới thiệu nhiều thành phần Material và các tính năng Flutter khác.
Thiết kế phân lớp của Flutter (video): Video này bao gồm thông tin về các widget trạng thái và không trạng thái. Được trình bày bởi kỹ sư của Google, Ian Hickson.
Giải phóng thời gian, khai phóng năng lực