Flutter 布局 Layout
由于 Flutter 的核心概念是 Everything is widget,因此 Flutter 将用户界面布局功能整合到了 widget 中。 Flutter 提供了很多专门设计的小部件,如 Container、Center、Align 等,仅用于布局用户界面。通过组合其他小部件构建的小部件通常使用布局小部件。 让我们在本章中学习 Flutter 布局概念。
Layout 小部件的类型
布局小部件可以根据其子元素分为两个不同的类别 -
- 支持单个子部件的小部件
- 支持多个子部件的小部件
让我们在接下来的部分中学习这两种类型的小部件及其功能。
单个子部件
在此类别中,小部件将只有一个小部件作为其子小部件,并且每个小部件都将具有特殊的布局功能。
例如,Center 小部件只是将它的子小部件相对于其父小部件居中,而 Container 小部件提供了完全的灵活性,可以使用不同的选项(如填充、装饰等)将其子部件放置在其内部的任何给定位置,
单个子小部件是创建具有单一功能(如按钮、标签等)的高质量小部件的绝佳选择,
使用 Container 小部件创建简单按钮的代码如下
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)
),
),
);
}
}
在这里,我们使用了两个小部件——一个容器小部件和一个文本小部件。 小部件的结果是一个自定义按钮,如下所示
让我们检查一下 Flutter 提供的一些最重要的单子布局小部件
- Padding - 用于通过给定的填充排列其子小部件。 在这里,填充可以由 EdgeInsets 类提供。
- Align - 使用对齐属性的值在自身内部对齐其子小部件。 对齐属性的值可以由 FractionalOffset 类提供。 FractionalOffset 类根据距左上角的距离指定偏移量。
一些可能的偏移值如下
- FractionalOffset(1.0, 0.0) 表示右上角。
- FractionalOffset(0.0, 1.0) 表示左下角。
关于偏移量的示例代码如下所示
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 - 它缩放子部件,然后根据指定的拟合定位它。
- AspectRatio - 它尝试将子部件调整为指定的纵横比
- ConstrainedBox
- Baseline
- FractinallySizedBox
- IntrinsicHeight
- IntrinsicWidth
- LiimitedBox
- OffStage
- OverflowBox
- SizedBox
- SizedOverflowBox
- Transform
- CustomSingleChildLayout
我们的 hello world 应用程序使用基于材质的布局小部件来设计主页。 让我们修改我们的 hello world 应用程序以使用下面指定的基本布局小部件构建主页 -
- Container - 通用、单子、基于框的容器小部件,具有对齐、填充、边框和边距以及丰富的样式功能。
- Center - 简单的单个子容器小部件,将其子小部件居中。
MyHomePage 和 MyApp 小部件的修改代码如下
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,
),
)
);
}
}
这里
- Container 小部件是顶级或根部件。 容器使用装饰和填充属性来配置其内容。
- BoxDecoration 有许多属性,如颜色、边框等,用于装饰 Container 小部件,这里使用颜色来设置容器的颜色。
- Container 小部件的填充是使用 dgeInsets 类设置的,它提供了指定填充值的选项。
- Center 是 Container 小部件的子小部件。 同样,Text 是 Center 小部件的子级。 Text 用于显示消息,Center 用于将文本消息相对于父窗口小部件 Container 居中。
上面给出的代码的最终结果是一个布局示例,如下所示
多个子部件
在这一类别中,给定的小部件将具有多个子部件,并且每个小部件的布局都是唯一的。
例如,Row 小部件允许其子级在水平方向上布局,而 Column 小部件允许其子级在垂直方向上布局。 通过组合 Row 和 Column,可以构建任何复杂程度的小部件。
让我们在本节中学习一些常用的小部件。
- Row - 允许以水平方式排列其子项。
- Column - 允许以垂直方式排列其子项。
- ListView - 允许将其子项排列为列表。
- GridView - 允许将其子项安排为画廊。
- Expanded - 用于使 Row 和 Column 小部件的子级占据最大可能区域。
- Table - 基于表格的小部件。
- Flow - 基于流的小部件。
- Stack - 基于堆栈的小部件。
高级布局应用
在本节中,让我们学习如何使用单个和多个子布局小部件创建具有自定义设计的产品列表的复杂用户界面。
为此,请按照下面给出的顺序
- 在 Android Studio 中新建一个 Flutter 应用程序 product_layout_app。
- 用以下代码替换 main.dart 代码
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// 这个小部件是应用程序的根。
@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', )),
);
}
}
这里,
我们通过扩展 StatelessWidget
而不是默认的 StatefulWidget 创建了 MyHomePage 小部件,然后删除了相关代码。
现在,根据指定的设计创建一个新的小部件 ProductBox,如下所示
ProductBox 的代码如下。
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()),
],
)
)
)
]
)
)
);
}
}
请在代码中遵守以下内容 -
ProductBox 使用了如下指定的四个参数 -
- name - product 名称
- description - Product 描述
- price - Product 价格
- image - Product 的图像
ProductBox 使用七个内置小部件,如下所示
- Container
- Expanded
- Row
- Column
- Card
- Text
- Image
ProductBox 是使用上述小部件设计的。 小部件的排列或层次结构在下图中指定
现在,在应用程序的 assets 文件夹中放置一些产品信息的虚拟图像(见下文),并在 pubspec.yaml 文件中配置 assets 文件夹,如下所示
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
最后,使用下面指定的 MyHomePage 小部件中的 ProductBox 小部件
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"
),
],
)
);
}
}
在这里,我们使用 ProductBox 作为 ListView 小部件的子级。
产品布局应用(product_layout_app)的完整代码(main.dart)如下
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()
),
],
)
)
)
]
)
)
);
}
}
应用程序的最终输出如下