重点 Widgets类用来构建UI Widgets用来做布局和UI元素 由简单的widget构建复杂widget
Flutter布局的核心机制是widgets. Flutter中, 几乎所有东西都是widget, layout模型也是widget. app上看得到的如images, icons, and text 也是widget. 看不到, 如rows, columns, and grids that arrange, constrain, align 也是widget.
通过组合widgets成复杂的widget来创建布局.
图解:
Container widget 允许你可以自定义它的子widget. 当想要增加padding, margins, borders, or background color 可以使用Container.
这个例子中, 每个Text widget放在一个Container中来添加margin. 整个Row也放在一个Container中来添加row周围的padding.
这个例子中, 其他的UI由属性控制. 通过color属性设置Icon的颜色. 通过Text.style属性设置字体, 颜色, 粗细等等. Columns 和 rows 属性, 如有vertically或horizontally对齐方式, 子widget占据的空间等等.
根据可见widget的对齐或约束方式, 选择对应的layout widgets.
下面例子使用 Center, 将内容水平和垂直居中.
创建Text widget
Text('Hello World'),
创建Image widget
Image.asset(
'images/lake.jpg',
fit: BoxFit.cover,
),
创建Icon widget
Icon(
Icons.star,
color: Colors.red[500],
),
所有的layout widget都有以下两种属性之一
- child属性, 如Center or Container
- children属性, 如Row, Column, ListView, or Stack
Center(
child: Text('Hello World'),
),
Flutter app本身就是一个widget, 大部分widget有一个build()方法. 在这个方法中实例化并返回一个widget来显示这个widget.
Material app中, 可以使用 Scaffold widget; 提供一个默认的banner, 背景色, API(adding drawers, snack bars, and bottom sheets). 设置Center widget到body属性来展示主页面.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter layout demo'),
),
body: Center(
child: Text('Hello World'),
),
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.white),
child: Center(
child: Text(
'Hello World',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 32,
color: Colors.black87,
),
),
),
);
}
}
non-Material app 默认不包含 AppBar, title, or background color. 如果想要有这些特性需要自己构建.
使用Row widget安排水平布局, Column widget安排垂直布局
添加一个children widgets list 到 Row 或 Column widget中. 每个child也可以是一个 row 或 column, 以此类推.
以下layout由一个Row, 包含两个children: 左边的column, 右边的image
左边的column内嵌了rows and columns
使用mainAxisAlignment和crossAxisAlignment属性控制一个row或column中children的对齐方式.
对于row, 主轴是水平的, 交叉轴是垂直的.
对于column, 主轴是垂直的, 交叉轴是水平的.
下面例子, 3张图片宽度都是100px, 渲染用的盒子(该案例为整个屏幕)超过300px. 设置MainAxisAlignment为spaceEvenly, 将水平的空间按前,中,后平均分配.
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
Columns 和 Row 类似. 设置MainAxisAlignment为spaceEvenly, 将水平的空间按上,中,下平均分配.
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
使用Expanded widget让尺寸适应row或者column. 将图片包在Expanded widget中来修复图片过大的问题.
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
Expanded widget的flex属性, 默认值为1. 设置这个值为2, 可以让这个widget占用空间是其他的两倍.
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
默认情况下, row或column会尽可能占用主轴的空间. 如果想要所有的子widget紧密的打包一起, 设置mainAxisSize为MainAxisSize.min
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
],
)
布局框架允许根据需求在行和列中嵌套行和列.
红框内容区域可以分为两个Row. 评价Row包含5个星星和评论数. 图标Row包含3个Column, 每一列包含icon和text.
评价Row布局如下:
ratings变量创建一个row包含row(包含5个星星图标)和text:
var stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
),
],
),
);
可以通过变量和函数来减少多层嵌套造成的视觉困扰
评分row下面的图标row中包含3个column; 每个column包含一个图标和两行字.
final descTextStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 18,
height: 2,
);
// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
// DefaultTextStyle.merge() 允许你创建一个所有子类都可以继承的默认文本样式
final iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: Container(
padding: EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
Text('PREP:'),
Text('25 min'),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
Text('COOK:'),
Text('1 hr'),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
Text('FEEDS:'),
Text('4-6'),
],
),
],
),
),
);
leftColumn变量包含标题, 子标题, 评分row和图标Row.
final leftColumn = Container(
padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(
children: [
titleText,
subTitle,
ratings,
iconList,
],
),
);
左边的列放在Container中来约束他的宽度. 最后, 装整个row(包含左边的列和右边的图)放在一个Card部件中.
body: Center(
child: Container(
margin: EdgeInsets.fromLTRB(0, 40, 0, 30),
height: 600,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 440,
child: leftColumn,
),
mainImage,
],
),
),
),
),
- widgets library 标准部件
- Material library 特殊部件
- Container: 给部件添加 padding, margins, borders, background color, 或其他装饰.
- GridView: 可滚动的格子.
- ListView: 可滚动的列表
- Stack: 在其他部件上面叠加部件
- Card: 将相关信息组织到带圆角和阴影的盒子中
- ListTile: 组织3行以内的文本, 可选的leading和trailing图标到一行中
Containers用法自由, 可以通过padding, borders 或 margins来分隔不同部件. 可以将一个layout部件放到整个Container中, 然后改变颜色或图片来作为背景.
- 添加 padding, margins, borders
- 改变背景色或图片
- 包含一个子部件(widget), 子部件可以是Row, Column,或部件树的根.
一个column中包含两row. Container用来改变背景色
Widget _buildImageColumn() => Container(
decoration: BoxDecoration(
color: Colors.black26,
),
child: Column(
children: [
_buildImageRow(1),
_buildImageRow(3),
],
),
);
Container也可以用来设置圆角, 边框
Widget _buildDecoratedImage(int imageIndex) => Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10, color: Colors.black38),
borderRadius: const BorderRadius.all(const 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),
],
);
二维列表布局. GridView默认提供两个列表,也可以自定义网格. GridView检测到内容太长, 会自动设置滚动.
- 网格布局
- 监听column内容何时超出盒子, 可以自动滚动
- 自定义网格, 或者使用提供好的网格
- GridView.count 指定column数
- GridView.extent 指定每一个tile的最大像素
- 使用GridView.extent创建一个grid, 并设置最大像素为150
Widget _buildGrid() => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30));
// The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg.
// The List.generate() constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Container> _buildGridTileList(int count) => List.generate(
count, (i) => Container(child: Image.asset('images/pic$i.jpg')));
- 使用GridView.count创建一个grid, 设置垂直模式下宽度为2个tile, 水平模式下3个tile.
ListView组件列, 当内容超出盒子会自动可滚动.
- 特殊的Column, 用于组织盒子列表
- 可以水平或垂直布局
- 自动检测内容可滚动化
- 易用, 支持滚动, 相对Column没那么可配置
使用ListView展示一列ListTile. 使用Divider作分隔线
Widget _buildList() => ListView(
children: [
_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
_tile('The Castro Theater', '429 Castro St', Icons.theaters),
_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
_tile('Roxie Theater', '3117 16th St', Icons.theaters),
_tile('United Artists Stonestown Twin', '501 Buckingham Way',
Icons.theaters),
_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
Divider(),
_tile('Kescaped_code#39;s Kitchen', '757 Monterey Blvd', Icons.restaurant),
_tile('Emmyescaped_code#39;s Restaurant', '1923 Ocean Ave', Icons.restaurant),
_tile(
'Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
_tile('La Ciccia', '291 30th St', Icons.restaurant),
],
);
ListTile _tile(String title, String subtitle, IconData icon) => ListTile(
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Colors.blue[500],
),
);
使用ListView展示Material Design palette的颜色
使用Stack将一个widget部件(通常是图片)放在基础部件widget上面. 可以完全或部分覆盖基础的部件.
- 用于一个部件覆盖另一个部件
- 子部件列表中的第一个widget为基础部件. 随后的部件会覆盖在该基础部件的上面.
- Stack的内容无法滚动
- 可以裁剪超出盒子的子部件
使用stack叠加一个Container
Widget _buildStack() => Stack(
alignment: const Alignment(0.6, 0.6),
children: [
CircleAvatar(
backgroundImage: AssetImage('images/pic.jpg'),
radius: 100,
),
Container(
decoration: BoxDecoration(
color: Colors.black45,
),
child: Text(
'Mia B',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
使用Stack进行梯度叠加到图片上, 确保toolbar的icon和图片区分开来
Card, 属于Material库, 包含许多有价值的信息, 可以由任何widget组成, 但是通常使用ListTile. Card只有一个子部件, 但子部件可以是column, row, list, grid,或其他支持多子部件的部件. 默认Card会收缩尺寸为0x0像素. 可以使用SizedBox来约束它的尺寸.
Card通过轻微圆角, 带阴影来提现3D效果. 修改elevation属性来控制阴影效果.
- Implements a Material card
- Used for presenting related nuggets of information
- Accepts a single child, but that child can be a Row, Column, or other widget that holds a list of children
- Displayed with rounded corners and a drop shadow
- A Card’s content can’t scroll
- From the Material library
Card中包含3个ListTiles, 然后包裹在SizedBox中设置尺寸. Divider设置分割线.
Widget _buildCard() => SizedBox(
height: 210,
child: Card(
child: Column(
children: [
ListTile(
title: Text('1625 Main Street',
style: TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text('My City, CA 99984'),
leading: Icon(
Icons.restaurant_menu,
color: Colors.blue[500],
),
),
Divider(),
ListTile(
title: Text('(408) 555-1212',
style: TextStyle(fontWeight: FontWeight.w500)),
leading: Icon(
Icons.contact_phone,
color: Colors.blue[500],
),
),
ListTile(
title: Text('[email protected]'),
leading: Icon(
Icons.contact_mail,
color: Colors.blue[500],
),
),
],
),
),
);
Card中包含图片和文本
Material库中特殊的行部件.最多3行文本, 可选的左边/右边的图标. 通常用在Card 或 ListView中, 也可以用在其他地方.
A specialized row that contains up to 3 lines of text and optional icons Less configurable than Row, but easier to use From the Material library
A Card containing 3 ListTiles.
Uses ListTile to list 3 drop down button types.