Flutter - Giới thiệu về bố cục
Vì khái niệm cốt lõi của Flutter là Mọi thứ đều là tiện ích , nên Flutter kết hợp chức năng bố cục giao diện người dùng vào chính các tiện ích. Flutter cung cấp khá nhiều widget được thiết kế đặc biệt như Container, Center, Align ,… chỉ nhằm mục đích bố trí giao diện người dùng. Các widget xây dựng bằng cách soạn các widget khác thường sử dụng các widget bố cục. Hãy sử dụng tìm hiểu khái niệm bố cục Flutter trong chương này.
Loại tiện ích bố cục
Các widget bố cục có thể được nhóm thành hai danh mục riêng biệt dựa trên con của nó -
- Widget hỗ trợ một đứa trẻ
- Widget hỗ trợ nhiều trẻ em
Hãy để chúng tôi tìm hiểu cả loại widget và chức năng của nó trong các phần sắp tới.
Tiện ích con đơn
Trong danh mục này, các widget sẽ chỉ có một widget là con của nó và mọi widget sẽ có một chức năng bố cục đặc biệt.
Ví dụ: tiện ích con Center chỉ căn giữa tiện ích con của nó đối với tiện ích con và tiện ích con Container cung cấp sự linh hoạt hoàn toàn để đặt nó ở bất kỳ vị trí nào bên trong nó bằng cách sử dụng các tùy chọn khác nhau như đệm, trang trí, v.v.
Các tiện ích con đơn lẻ là các tùy chọn tuyệt vời để tạo tiện ích con chất lượng cao có chức năng đơn lẻ như nút, nhãn, v.v.,
Mã để tạo một nút đơn giản bằng widget Container như sau:
class MyButton extends StatelessWidget {
MyButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
left: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
right: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
bottom: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
),
),
child: Container(
padding: const
EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
left: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
right: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
bottom: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
),
color: Colors.grey,
),
child: const Text(
'OK',textAlign: TextAlign.center, style: TextStyle(color: Colors.black)
),
),
);
}
}
Ở đây, chúng tôi đã sử dụng hai widget - một widget Container và một widget Văn bản . Kết quả của tiện ích là một nút tùy chỉnh như hình dưới đây -
Hãy để chúng tôi kiểm tra một số widget bố cục con đơn lẻ quan trọng nhất do Flutter cung cấp -
Padding- Được sử dụng để sắp xếp widget con của nó bằng khoảng đệm đã cho. Ở đây, padding có thể được cung cấp bởi lớp EdgeInsets .
Align- Căn chỉnh widget con của nó trong chính nó bằng cách sử dụng giá trị của thuộc tính căn chỉnh . Giá trị cho thuộc tính căn chỉnh có thể được cung cấp bởi lớp FractionalOffset . Lớp FractionalOffset chỉ định các hiệu số về khoảng cách từ phía trên bên trái.
Một số giá trị có thể có của hiệu số như sau:
FractionalOffset (1,0, 0,0) đại diện cho phía trên bên phải.
FractionalOffset (0.0, 1.0) đại diện cho phía dưới bên trái.
Dưới đây là một đoạn mã mẫu về hiệu số:
Center(
child: Container(
height: 100.0,
width: 100.0,
color: Colors.yellow, child: Align(
alignment: FractionalOffset(0.2, 0.6),
child: Container( height: 40.0, width:
40.0, color: Colors.red,
),
),
),
)
FittedBox - Nó chia tỷ lệ widget con và sau đó định vị nó theo sự phù hợp đã chỉ định.
AspectRatio - Nó cố gắng kích thước tiện ích con theo tỷ lệ khung hình được chỉ định
ConstrainedBox
Baseline
FractinallySizedBox
IntrinsicHeight
IntrinsicWidth
LiimitedBox
OffStage
OverflowBox
SizedBox
SizedOverflowBox
Transform
CustomSingleChildLayout
Ứng dụng hello world của chúng tôi đang sử dụng các widget bố cục dựa trên vật liệu để thiết kế trang chủ. Hãy để chúng tôi sửa đổi ứng dụng hello world của mình để tạo trang chủ bằng cách sử dụng các widget bố cục cơ bản như được chỉ định bên dưới -
Container - Tiện ích vùng chứa chung, con đơn, dựa trên hộp với căn chỉnh, đệm, đường viền và lề cùng với các tính năng tạo kiểu phong phú.
Center - Đơn giản, một widget vùng chứa con, tập trung vào widget con của nó.
Mã sửa đổi của tiện ích MyHomePage và MyApp như sau:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MyHomePage(title: "Hello World demo app");
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.white,),
padding: EdgeInsets.all(25), child: Center(
child:Text(
'Hello World', style: TextStyle(
color: Colors.black, letterSpacing: 0.5, fontSize: 20,
),
textDirection: TextDirection.ltr,
),
)
);
}
}
Đây,
Tiện ích vùng chứa là tiện ích cấp cao nhất hoặc tiện ích gốc. Vùng chứa được định cấu hình bằng cách sử dụng thuộc tính trang trí và phần đệm để bố trí nội dung của nó.
BoxDecoration có nhiều thuộc tính như màu sắc, đường viền, v.v., để trang trí cho widget Container và ở đây, color được sử dụng để thiết lập màu của container.
padding của widget Container được đặt bằng cách sử dụng lớp dgeInsets , lớp này cung cấp tùy chọn để chỉ định giá trị padding.
Center là widget con của widget Container . Một lần nữa, Văn bản là con của tiện ích Trung tâm . Văn bản được sử dụng để hiển thị tin nhắn và Trung tâm được sử dụng để căn giữa tin nhắn văn bản đối với tiện ích con, Vùng chứa .
Kết quả cuối cùng của đoạn mã được đưa ra ở trên là một mẫu bố cục như hình dưới đây:
Nhiều tiện ích con
Trong danh mục này, một widget nhất định sẽ có nhiều hơn một widget con và bố cục của mỗi widget là duy nhất.
Ví dụ: tiện ích con Hàng cho phép bố trí các con của nó theo hướng ngang, trong khi tiện ích Cột cho phép bố trí các con của nó theo hướng dọc. Bằng cách tạo Hàng và Cột , có thể tạo tiện ích con với bất kỳ mức độ phức tạp nào.
Hãy cùng chúng tôi tìm hiểu một số widget thường dùng trong phần này.
Row - Cho phép sắp xếp các con của nó theo chiều ngang.
Column - Cho phép sắp xếp các con của nó theo chiều dọc.
ListView - Cho phép sắp xếp các con của nó dưới dạng danh sách.
GridView - Cho phép sắp xếp con cái của nó như một bộ sưu tập.
Expanded - Được sử dụng để làm cho widget con của Row và Column chiếm diện tích tối đa có thể.
Table - Phụ tùng dựa trên bảng.
Flow - Widget dựa trên dòng chảy.
Stack - Widget dựa trên ngăn xếp.
Ứng dụng bố cục nâng cao
Trong phần này, chúng ta hãy tìm hiểu cách tạo giao diện người dùng phức tạp của danh sách sản phẩm với thiết kế tùy chỉnh bằng cách sử dụng cả tiện ích con bố cục một và nhiều.
Với mục đích này, hãy làm theo trình tự được đưa ra dưới đây:
Tạo ứng dụng Flutter mới trong Android studio, product_layout_app .
Thay thế mã main.dart bằng mã sau -
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(
primarySwatch: Colors.blue,),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title),),
body: Center(child: Text( 'Hello World', )),
);
}
}
Here,
Chúng tôi đã tạo tiện ích MyHomePage bằng cách mở rộng StatelessWidget thay vì StatefulWidget mặc định và sau đó xóa mã liên quan.
Bây giờ, tạo một widget mới, ProductBox theo thiết kế được chỉ định như hình dưới đây -
Mã cho ProductBox như sau.
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image})
: super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2), height: 120, child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
Image.asset("assets/appimages/" +image), Expanded(
child: Container(
padding: EdgeInsets.all(5), child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(this.name, style: TextStyle(fontWeight:
FontWeight.bold)), Text(this.description),
Text("Price: " + this.price.toString()),
],
)
)
)
]
)
)
);
}
}
Vui lòng quan sát những điều sau trong đoạn mã:
ProductBox đã sử dụng bốn đối số như được chỉ định bên dưới:
name - Tên sản phẩm
mô tả - Mô tả sản phẩm
price - Giá của sản phẩm
hình ảnh - Hình ảnh của sản phẩm
ProductBox sử dụng bảy tiện ích con tích hợp như được chỉ định bên dưới -
- Container
- Expanded
- Row
- Column
- Card
- Text
- Image
ProductBox được thiết kế bằng tiện ích được đề cập ở trên. Sự sắp xếp hoặc phân cấp của tiện ích được chỉ định trong sơ đồ hiển thị bên dưới -
Bây giờ, đặt một số hình ảnh giả (xem bên dưới) để biết thông tin sản phẩm trong thư mục nội dung của ứng dụng và định cấu hình thư mục nội dung trong tệp pubspec.yaml như hình dưới đây -
assets:
- assets/appimages/floppy.png
- assets/appimages/iphone.png
- assets/appimages/laptop.png
- assets/appimages/pendrive.png
- assets/appimages/pixel.png
- assets/appimages/tablet.png
iPhone.png
Pixel.png
Laptop.png
Tablet.png
Pendrive.png
Floppy.png
Cuối cùng, Sử dụng tiện ích ProductBox trong tiện ích MyHomePage như được chỉ định bên dưới -
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text("Product Listing")),
body: ListView(
shrinkWrap: true, padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget> [
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"
),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"
),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"
),
],
)
);
}
}
Ở đây, chúng tôi đã sử dụng ProductBox làm con của tiện ích ListView .
Mã hoàn chỉnh (main.dart) của ứng dụng bố cục sản phẩm (product_layout_app) như sau:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iphone.png"
),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"
),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.png"
),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.png"
),
ProductBox(
name: "Pendrive",
description: "Pendrive is useful storage medium",
price: 100,
image: "pendrive.png"
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.png"
),
],
)
);
}
}
class ProductBox extends StatelessWidget {
ProductBox({Key key, this.name, this.description, this.price, this.image}) :
super(key: key);
final String name;
final String description;
final int price;
final String image;
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset("assets/appimages/" + image),
Expanded(
child: Container(
padding: EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
this.name, style: TextStyle(
fontWeight: FontWeight.bold
)
),
Text(this.description), Text(
"Price: " + this.price.toString()
),
],
)
)
)
]
)
)
);
}
}
Kết quả cuối cùng của ứng dụng như sau: