Flutter中的布局
在Flutter中,布局的核心机制是
widgets
,一切事物都是由widgets
构成。
以下是UI的一个widgets树形图
对于widgets中,里面有两个元素最为重要:
- 一个是
child
属性,如果只包含一个子项,例如Center
和Container
- 一个是
children
属性,如果包含多个子项,例如Row
和Column
、ListView
等
如果我们想要将自己的UI widgets实现,那么就需要继承Flutter所提供的widgets,当然,大都数widgets都会提供一个build()
方法
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter layout demo'),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
当然,你可以使用不同的类型,例如非MaterialApp
,也是使用同样的方法进行构建。
横向或纵向布局多个widgets
我们最常见的一种布局就是垂直或水平的widgets,Row
自然是横向,Column
就是纵向。
对于航向和纵向而言,其内部都可以有子widgets
列表,因此可以有多种组合。
就比如上述图片中所表现的:
- 总体上是由一个大的
Row
横向widget构成 - 内部由一个子widget:
Column
和Image widget
构成 - 左边的
Column
中又可以分为多个子widgets
那么,我们现在就依照这一种布局模式,来详细解析。
第一步,构建大框架
所谓的大框架,那么就是上述所说的,左边是
Row widget
,右边是Image widget
Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
margin: const EdgeInsets.fromLTRB(0, 40, 0, 30),
height: 600,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 440,
child: leftColumn,
),
mainImage,
],
),
),
),
),
);
Scaffold
是Flutter中的一个基本widgets
,可以提供AppBar
(顶部)、底部的导航栏、抽屉选单等。body
属性就是我们需要设置的主要区域,可以看见,在Center
中我们主要有两个child
:SizedBox
和mainImage
- 使用
Card widgets
的原因是,可以把展示内容和图片以卡片的形式展现出来
第二步,解析子widgets
解析mainImage
mainImage
实际上就是右边的Image widgets
:
final mainImage = Image.asset(
'./images/pavlova.jpg',
fit: BoxFit.cover,
);
解析leftColumn
leftColumn
实际上是一个自定义的子widgets
final leftColumn = Container(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(
children: [
titleText,
subTitle,
ratings,
iconList,
],
),
);
// 左边的标题
const titleText = Padding(
padding: EdgeInsets.all(20),
child: Text(
'Strawberry Pavlova',
style: TextStyle(
fontWeight: FontWeight.w800,
letterSpacing: 0.5,
fontSize: 30,
),
),
);
const subTitle = Text(
'Pavlova is a meringue-based dessert named after the Russian ballerina '
'Anna Pavlova. Pavlova features a crisp crust and soft, light inside, '
'topped with fruit and whipped cream.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Georgia',
fontSize: 25,
),
);
// 类似于打评分的星
var stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Color.fromRGBO(0, 255, 0, 0.5)),
Icon(Icons.star, color: Color.fromRGBO(0, 255, 0, 0.5)),
Icon(Icons.star, color: Color.fromRGBO(0, 255, 0, 0.5)),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
const Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
)
],
),
);
final iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
const Text('PREP:'),
const Text('25 min'),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
const Text('COOK:'),
const Text('1 hr'),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
const Text('FEEDS:'),
const Text('4-6'),
],
),
],
),
),
);
- 可以看见,左边的子widgets由四个小widgets构成,我们只需要分别实现四个部分,然后并入最后的
leftColumn
即可。
如下是rating行的构成树形图
如下是iconList行的构成树形图
通用布局widgets
Flutter中一般会将widgets分为两类:标准widgets
和Material widgets
,需要注意的是任何App都可以使用标准widgets,Material组件只能在MaterialApp中使用
标准widgets | 特征 | Material | 特征 |
---|---|---|---|
Container | 向widgets中增加padding、margins、borders等装饰 | Card | 将相关信息整理到一个有圆角和阴影的盒子中 |
GridView | 将widgets展示为一个可滚动的网格 | ListTile | 将最多三行的文本、可选的导语以及后面的图标组织在一行中 |
ListView | 将widgets展示为一个可滚动的列表 | ||
Stack | 将widgets覆盖在另一个的上面 |
Container
任何布局都可以任意使用Container
一般地,Container
可以修改背景色或者样式图片。
Widget _buildImageColumn() {
return Container(
decoration: const BoxDecoration(
color: Colors.black26,
),
child: Column(
children: [
_buildImageRow(1),
_buildImageRow(3),
],
),
);
}
Widget _buildDecoratedImage(int imageIndex) => Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10, color: Colors.black38),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
margin: const EdgeInsets.all(4),
child: Image.asset('images/pic$imageIndex.jpg'),
),
);
Widget _buildImageRow(int imageIndex) => Row(
children: [
_buildDecoratedImage(imageIndex),
_buildDecoratedImage(imageIndex + 1),
],
);
- 在
BoxDecoration widget
中的borderRadius
属性能够设置圆角效果。
GridView
GridView
将widgets作为二维列表展示,GridView
提供两个预制的列表,如果检测到内容太长而无法适应渲染盒时,则会自动支持滚动
ListView
ListView
是一个和Column
很像的widget,当内容长于自己的渲染盒时,会自动支持滚动
- 相较于
Column
:- 可以水平或者垂直布局
- 检测空间不足时,会提供滚动
- 配置更少,使用更容易,并且支持滚动
Stack
可以使用Stack
在基础widget上排列widget
- 其主要用于覆盖另一个
Stack
- 子列表的第一个
widget
是基础,后面的子项覆盖在基础widget
的顶部 Stack
的内容是无法滚动的
在CircleAvatar
的上面使用Stack
覆盖Container
(在透明的黑色背景上展示它的 Text
)。Stack
使用alignment
属性和 Alignment
让文本偏移。
Card
Material
库 中的 Card
包含相关有价值的信息,几乎可以由任何 widget
组成,但是通常和 ListTile
一起使用。 Card
只有一个子项,这个子项可以是列、行、列表、网格或者其他支持多个子项的 widget
。默认情况下,Card
的大小是 0x0
像素。你可以使用 SizedBox
控制 card
的大小。
在 Flutter 中,Card
有轻微的圆角和阴影来使它具有 3D 效果。改变 Card
的 elevation
属性可以控制阴影效果。例如,把 elevation
设置为 24,可以从视觉上更多的把 Card
抬离表面,使阴影变得更加分散。关于支持的 elevation
的值的列表,可以查看 Material guidelines
中的 Elevation
。使用不支持的值则会使阴影无效。
ListTile
ListTile
是 Material
库 中专用的行widget
,它可以很轻松的创建一个包含三行文本以及可选的行前和行尾图标的行。 ListTile
在 Card
或者 ListView
中最常用,但是也可以在别处使用。