https://flutter.io/tutorials/layout/
可以学到:
Flutter的布局机制
如何水平与垂直布局控件
如何构建一个Flutter布局
布局的效果图:https://flutter.io/tutorials/layout/images/lakes.jpg
这篇指南,分步介绍了Flutter的布局方式,展示了如何在屏幕放一个控件.在介绍如何布局前,先来看一看包含的范围:
构建一个布局
如果想了解Flutter的布局机制,可以查看https://flutter.io/tutorials/layout/#approach
第0步:
设置环境,创建一个基础的Flutter应用.这一步不再赘述
添加一个图片到项目的顶层叫images.添加lage.jpg到这个目录中https://github.com/flutter/website/blob/master/_includes/code/layout/lakes/images/lake.jpg
更新pubspec.yaml文件,包含进assets
assets:
- images/lake.jpg
步骤1:
这步,分为以下几步:
确定行与列
布局是否包含网格
有没有遮罩的元素
ui是否需要tab
通知区域需要垂直定位,补白边框等
首先,确定最大元素.示例中,四个元素在一列,图片,两行,一个文本.
效果图:https://flutter.io/tutorials/layout/images/lakes-diagram.png
两行内容的效果图:https://flutter.io/tutorials/layout/images/lakes-diagram.png
第一行,是标题,有三个子元素.一列文本,一个star图标,一个数字.第一列,包含两个文本,第一个文本有较大空间,需要折叠的.
第二行效果图:https://flutter.io/tutorials/layout/images/button-section-diagram.png
按钮段,有三个元素,每一个子元素是一列,包含一个图标与文本.
一旦布局图形化,就很容易地自下而上的去实现它.
步骤2:实现标题行
先构建左侧的标题.将这一列放到可展开的控件中.让它充满这个剩下的空间.设置crossAxisAlignment属性到crossAxisAlignment.start行的开始位置.
将文本第一行放到一个容器中,添加padding.第二个子元素,也是文本,显示颜色是grey.
剩下的两个项是图标.为红色,文本为"41",将事先 行放到容器中,边的补白是32像素.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = Container(
padding: const EdgeInsets.all(32.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Text(
'Kandersteg, Switzerland',
style: TextStyle(
color: Colors.grey[500],
),
),
],
),
),
Icon(
Icons.star,
color: Colors.red[500],
),
Text('41'),
],
),
);
//...
}
步骤3,实现按钮行
按钮行包含三个按钮,使用同样的布局.一个图标在文本上.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//...
}
每一行几乎是确定的,可以很高效地创建内部方法.如buildButtonColumn(),它包含文本与图标.文字是主色
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Column buildButtonColumn(IconData icon, String label) {
Color color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8.0),
child: Text(
label,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
//...
}
build方法,添加一个图标到列中,放一个文本到容器里,添加补白与图标分开.
行包含三个列.所以作为参数传入图标与文本.列在主轴方向,使用MainAxisAlignment.spaceEvenly来使它充满剩下的空间.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Widget buttonSection = Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildButtonColumn(Icons.call, 'CALL'),
buildButtonColumn(Icons.near_me, 'ROUTE'),
buildButtonColumn(Icons.share, 'SHARE'),
],
),
);
//...
}
步骤4,实现文本段
定义文本段,将一个长文本定义一个变量.将它放入容器中,32像素的补白边.softwrap属性表明可折叠.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Widget textSection = Container(
padding: const EdgeInsets.all(32.0),
child: Text(
'''
Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, followed by a half-hour walk through pastures and pine forest, leads you to the lake, which warms to 20 degrees Celsius in the summer. Activities enjoyed here include rowing, and riding the summer toboggan run.
''',
softWrap: true,
),
);
//...
}
步骤5:实现图标段
四列元素的三个已经完成了.剩下图片.图片获取比较慢,虽然它是通用的协议.步骤0已经添加了图片了.现在引用它.
在build最后
return MaterialApp(
//...
body: ListView(
children: [
Image.asset(
'images/lake.jpg',
height: 240.0,
fit: BoxFit.cover,
),
// ...
],
),
//...
);
步骤6,所有放一起
这些控件放到一个ListView,而不是一列中,因为ListView可以实现自己的滚动,特别是小屏上.
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Top Lakes'),
),
body: ListView(
children: [
Image.asset(
'images/lake.jpg',
width: 600.0,
height: 240.0,
fit: BoxFit.cover,
),
titleSection,
buttonSection,
textSection,
],
),
),
);
//...
代码地址:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/main.dart
https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes/pubspec.yaml
当热加载或编译运行时,就可以看到效果了.
Flutter的布局方式:
控件类用于构建ui
控件用于ui元素与布局.
组合简单的控件来构建复杂的控件.
Flutter的核心布局机制是控件.在Flutter,几乎所有的东西都是一个控件.甚至布局模型也是控件.图片,图标,文本是最常用的控件.你看不到的也是,像行,列,网格,约束等.
组合一个复杂的控件,像屏幕中展示的,左边三个图标,一个文本在下面的控件.https://flutter.io/tutorials/layout/images/lakes-icons.png
https://flutter.io/tutorials/layout/images/lakes-icons-visual.png展示了可视化的布局,显示了各控件.
看一下控件树的图形:
https://flutter.io/tutorials/layout/images/sample-flutter-layout.png
看到你效果,你可能会想容器.容器是一个控件,允许你自定义子控件.使用容器Container,要添加padding,margins,borders,或背景色.
示例中,文本控件放在容器中,有margins.整个行也有padding.
剩下的示例中 的ui,通过属性控制,使用color设置按钮的颜色.使用style属性来设置文本的字体,颜色,权重等.行列也有属性.可以设置垂直与水平方向的定位占比.
布局一个控件.
app本身也是一个控件
很容易创建控件与添加到布局中.
添加到布局中,显示在设备上
很容易使用Scaffold,它是Material组件库中的控件,提供默认的banner,背景色,有添加drawer,snackbar ,bottom sheets的api.
如果你喜欢,可以构建一个app,使用默认的库中的控件.
在Flutter如何布局一个单一的控件.这音展示如何创建一个简单的控件,然后展示在屏幕中.
就像hello world中一样,没太多需要介绍的
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
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: 40.0, color: Colors.black87)),
),
);
}
}
效果图:https://flutter.io/tutorials/layout/images/hello-world.png
非Material的app没有appbar,标题,背景等.如果需要这些.可以自己加.
Material风格的:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/hello-world/main.dart
只包含控件的:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/widgets-only/main.dart
水平或垂直布局多个控件:
最常用的布局模式是将控件水平或垂直地排放.可以使用Row控件水平排放,使用Column控件垂直排放.
Row与Column是最常用的布局模式
Row与Column都有一个子控件列表.
子控件可以自己是一个Row或Column或其它复杂的控件.
可以同时指定子元素的垂直与水平位置
可以拉伸或约束子控件.
可以分配子元素如何使用空间.
内容:
在Flutter里面创建行或列,然后添加子元素列表到行或列控件中.下面展示如何嵌套
这个布局组织为一行,行包含两个子元素,一列在左,一个图标在右.https://flutter.io/tutorials/layout/images/pavlova-diagram.png
左侧是一个嵌套的控件.https://flutter.io/tutorials/layout/images/pavlova-left-column-diagram.png
实现代码https://flutter.io/tutorials/layout/#nesting.
行列是基本的元素,这些低阶控件,允许你最大化地自定义.Flutter也提供了一些高阶的控件,来满足你的需求.如,可以使用ListTile来替代行,它是很容易使用的控件,有图标,三行文本.而取代列的有ListView,它自适应滚动.
排列控件
你使用mainAxisAlignment与crossAxisAlignment属性来控制着如何安排子元素.对于行,主轴是水平的.纵轴是垂直的.对于列则相反.
https://flutter.io/tutorials/layout/images/row-diagram.png,https://flutter.io/tutorials/layout/images/column-diagram.png
接下来的示例中,三个图像是100像素宽,渲染框是大于300像素.所以设置主轴的排列是spaceEvenly,平分分割水平方向的空间.
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
https://flutter.io/tutorials/layout/images/row-spaceevenly-visual.png
列与行是一样的.
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
https://flutter.io/tutorials/layout/images/column-visual.png
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/column/main.dart
https://github.com/flutter/website/tree/master/_includes/code/layout/column/images
当一个布局太大了,会出现红色的条在边上.https://flutter.io/tutorials/layout/images/layout-too-large.png
控件可以缩放来适应,这时要使用Expanded控件.
对控件缩放大小
如果你的控件是两倍于兄弟控件的大小.可以放到Expanded控件中,控件着它的主轴方向的大小.它有flex属性,是整型值,测量flex因子.默认是1.这就像weight一样.
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
Expanded(
效果:https://flutter.io/tutorials/layout/images/row-expanded-visual.png
三个元素,中间是旁边的两倍大小,所以flex:2
当空间太大,会有红色的边框,https://flutter.io/tutorials/layout/images/row-expanded-2-visual.png,放到Expanded就可以了
打包控件
默认的行与列会尽量占用主轴的空间.如果你要打包起来,就设置mainAxisSize到最小MainAxisSize.min
.
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
var packedRow = 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),
],
);
// ...
}
https://flutter.io/tutorials/layout/images/packed.png 这种就是居中显示,所有控件向中间靠拢.它不像android里的居中定位,所以是通过设置轴上的属性来实现的
嵌套的列与行
布局框架,允许你嵌套行与列,只要你喜欢.https://flutter.io/tutorials/layout/images/pavlova-large-annotated.png
概述章节为两行实现的.rating行由五个星图标实现.图标行,包含三列图与文本.
rating行的控件树:https://flutter.io/tutorials/layout/images/widget-tree-pavlova-rating-row.png
rating变量,创建一个行,包含五个星图标.与文本
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//...
var ratings = Container(
padding: EdgeInsets.all(20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
Icon(Icons.star, color: Colors.black),
],
),
Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20.0,
),
),
],
),
);
//...
}
}
它的下面,包含三列.每一列包含一个图标与两行文本.控件树:https://flutter.io/tutorials/layout/images/widget-tree-pavlova-icon-row.png
iconList变量定义在icons行中
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
// ...
var descTextStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 18.0,
height: 2.0,
);
// DefaultTextStyle.merge allows you to create a default text
// style that is inherited by its child and all subsequent children.
var iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: Container(
padding: EdgeInsets.all(20.0),
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变量,包含rating图标.
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//...
var leftColumn = Container(
padding: EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0),
child: Column(
children: [
titleText,
subTitle,
ratings,
iconList,
],
),
);
//...
}
}
leftColumn放在容器中,宽受约束,整个ui放在一个卡片里.
使用Image.network来内嵌图片,但这个示例图片是放在images目录中的.并添加到pubspec文件里.
body: Center(
child: Container(
margin: EdgeInsets.fromLTRB(0.0, 40.0, 0.0, 30.0),
height: 600.0,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 440.0,
child: leftColumn,
),
mainImage,
],
),
),
),
),
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/pavlova/main.dart
图片:https://github.com/flutter/website/tree/master/_includes/code/layout/pavlova/images
配置:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/pavlova/pubspec.yaml
通用布局控件:
Flutter有丰富的布局控件,有一些比较常用.https://docs.flutter.io/文档可以查到更多关于控件的使用.这些控件主要是让用户可以容易地实现需求.
控件,分为两类,普通的控件与Material设计库.但只有Material的app可以使用Material库.
-
Container
Adds padding, margins, borders, background color, or other decorations to a widget.
-
GridView
Lays widgets out as a scrollable grid.
-
ListView
Lays widgets out as a scrollable list.
-
Stack
Overlaps a widget on top of another.
Material Components
-
Card
Organizes related info into a box with rounded corners and a drop shadow.
-
ListTile
Organizes up to 3 lines of text, and optional leading and trailing icons, into a row.
容器:
很多布局,大量使用容器来分割控件,这些容器带有边框,空白等.通过将整个布局放在容器中,可以修改设备的背景色.
容器总结:
添加padding,margin,border
改变背景色
包含单一的控件,可以是行,列或根
容器示例
可以在https://github.com/flutter/flutter/tree/master/examples/flutter_gallery中查找到使用的示例.
这个布局包含一个列,每列两行,有两个图片,每个图片,包含灰边与间距,列使用容器来设置背景色.
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/container/main.dart
图片:https://github.com/flutter/website/tree/master/_includes/code/layout/container/images
配置:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/container/pubspec.yaml
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
var container = Container(
decoration: BoxDecoration(
color: Colors.black26,
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10.0, color: Colors.black38),
borderRadius:
const BorderRadius.all(const Radius.circular(8.0)),
),
margin: const EdgeInsets.all(4.0),
child: Image.asset('images/pic1.jpg'),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10.0, color: Colors.black38),
borderRadius:
const BorderRadius.all(const Radius.circular(8.0)),
),
margin: const EdgeInsets.all(4.0),
child: Image.asset('images/pic2.jpg'),
),
),
],
),
// ...
// See the definition for the second row on GitHub:
// https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/container/main.dart
],
),
);
//...
}
}
网格:
GridView是二维的列表.当它太大时,会自己滚动.可以使用预设也可以自定义.
总结:
网格外放控件
检测到列超过渲染框时提供滚动.
有两个构建方式.GridView.count允许几列.GridView.extent允许最大宽像素.
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/grid/main.dart
代码:https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart
// The images are saved with names pic1.jpg, pic2.jpg...pic30.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) {
return List<Container>.generate(
count,
(int index) =>
Container(child: Image.asset('images/pic${index+1}.jpg')));
}
Widget buildGrid() {
return GridView.extent(
maxCrossAxisExtent: 150.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: _buildGridTileList(30));
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: buildGrid(),
),
);
}
}
列表:列表是一个列的控件,当内容超过渲染框时自动滚动
总结:一个特定box的列表
可以水平或垂直布局
超过渲染框范围会自动滚动.
比Column更容易实现滚动.配置更少.
列表示例:
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/listview/main.dart
gallery代码示例:https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/colors_demo.dart
List<Widget> list = <Widget>[
ListTile(
title: Text('CineArts at the Empire',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: Text('85 W Portal Ave'),
leading: Icon(
Icons.theaters,
color: Colors.blue[500],
),
),
ListTile(
title: Text('The Castro Theater',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: Text('429 Castro St'),
leading: Icon(
Icons.theaters,
color: Colors.blue[500],
),
),
// ...
// See the rest of the column defined on GitHub:
// https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/listview/main.dart
];
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
// ...
body: Center(
child: ListView(
children: list,
),
),
);
}
}
栈:
使用栈来排列控件.通常是在顶部的 图片上使用.这个控件可以部分或全部浮在基础控件上.
在悬浮另一个控件上使用.
列表中的第一个元素,是一个基础擦伤,然后第二个在上面.
栈的内容不可滚动.
可以选择是否切割来显示超过部分.
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/stack/main.dart
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
var stack = Stack(
alignment: const Alignment(0.6, 0.6),
children: [
CircleAvatar(
backgroundImage: AssetImage('images/pic.jpg'),
radius: 100.0,
),
Container(
decoration: BoxDecoration(
color: Colors.black45,
),
child: Text(
'Mia B',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
// ...
}
}
卡片:
卡片是Material组件库中的,可以组合几乎所有控件.但它经常与ListTile一起使用.卡片是有单一的子元素.这个子元素可 以是行,或,列表,网格.默认的卡片是0*0像素,可以使用SizedBox来约束大小.
在Flutter,卡片特性有圆角与阴影,像3d效果.改变卡片的elevation属性,允许你控制阴影效果.这点像Android里面的一样.查看
https://material.io/guidelines/material-design/elevation-shadows.html可以知道支持的属性.
总结:
来自于Material设计.实现了Material卡片
像砖块的形式展现
接受单一的子元素,子元素可以包含各种控件.
带有圆角与阴影边
不可滚动.
示例:
代码:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/card/main.dart
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
var card = SizedBox(
height: 210.0,
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('costa@example.com'),
leading: Icon(
Icons.contact_mail,
color: Colors.blue[500],
),
),
],
),
),
);
//...
}
ListTile
使用ListTile作为一个定制化的行控件,来自Material库,可以容易地创建一三行文本,一个可选的首尾图标.它经常与卡片与ListView共用.
示例:
代码:https://flutter.io/tutorials/layout/#card-examples
资源:
下面的资源帮助你编写布局:
翻译的不太好.像其它框架一个,react,weex,一切都是控件,然后用这些控件去拼接起来,整体上会比单独在Android或ios上要复杂一点.布局中,使用的是轴的概念,因为允许垂直与水平方向的.基于目前的ide与插件,开发比较耗事,无法预览是个问题, 但ios开发人员可能比较容易适应它.Android习惯了xml布局会有些迷茫,整体思路都类似的.控件就是上下左右摆放,然后设置一些间距等.