你将学习: - Flutter布局机制如何工作 - 如何竖直或横向展示组件 - 如何构建Flutter布局 |
这篇文章说明Flutter搭建布局。我们将学习搭建布局,做种效果如下截图:
这篇引导退一步来解释Flutter进行布局的方式,以及展示如何在屏幕上放置一个单独的组件。在学习完如何横向或竖向展示组件之后,我们会再看到些常用的布局组件。
若你想理解“big picture”的布局原理,那么需要学习Flutter布局方法。
首页获取代码:
下来在工程中添加图片:
第一步是将布局分解成基本元素:
首先,识别更大的元素。在这里,四个元素在同一列中:一个图片,两行和一个文本块。
接下来,图解每行。第一行,我们称其Title Section,有3个子组件:一列文本区域,一个星型图标,及一个数字。第一列子组件包含2行文本。且第一列占有较大空间,因此需要将两行文本放在Expanded组件中。
第二行,我们称其Button section,同样有3个子组件:由三列组成,且每列均由一个图标和文本组成。
在图解了布局之后,再从细节到整体来实现这个布局就容易了。为了让嵌套的代码看起来不那么混乱,我们将一些实现置于变量和函数中。
首先需要在Title Section左侧创建一列。在Expanded组件中的Column组件使得当前列(column并非组件)可以覆盖真个Title section. 将Column组件的 crossAxisAlignment 属性设置为CrossAxisAlignment.start ,这样Column组件位于当前行的起始位置。
将第一行的文本组件放置于Container组件中以便添加Container内边据。第二个文本组件文字是灰色。
最后的2个组件包括一个红色星型图标和一个数字“41”的文本。将整个标题行(Title Section图解中的Row with 3 children)放置在一个Container组件中,并且设置Container组件32px的内边距。
Note: 如何代码实现有问题,可以依据Github上的lib/main.dart 来检查你的代码。 |
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = new Container(
padding: const EdgeInsets.all(32.0),
child: new Row(
children: [
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new Container(
padding: const EdgeInsets.only(bottom: 8.0),
child: new Text(
'Oeschinen Lake Campground',
style: new TextStyle(
fontWeight: FontWeight.bold,
),
),
),
new Text(
'Kandersteg, Switzerland',
style: new TextStyle(
color: Colors.grey[500],
),
),
],
),
),
new Icon(
Icons.star,
color: Colors.red[500],
),
new Text('41'),
],
),
);
//...
}
Tip: 粘贴代码到工程中时,代码缩进可能错乱。如果是在IntelliJ中,可以有单机选择Reformat with Dart Style。或者在命令行中使用dartfmt命令。 |
Tip: 为体验更快开发过程,尝试使用Flutter的热加载功能。热加载使得在修改代码同时快速地在查看到修改后的效果,而不用重运行app。 |
Button Section包含3列相同的布局——一个图标和一个文本。这行中3列均匀分布,并且文本和图标颜色是APP build()方法中设置的primary color。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// title section implementation
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
//...
}
由于创建每列的代码是相同的,最高效的办法就是创建一个嵌套函数,例如就定义为buildButtonColumn(),这个方法中创建包含一个图标和一个文本得组件,并且返回Column对象。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Column buildButtonColumn(IconData icon, String label) {
Color color = Theme.of(context).primaryColor;
return new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Icon(icon, color: color),
new Container(
margin: const EdgeInsets.only(top: 8.0),
child: new Text(
label,
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
//...
}
这个创建方法中直接添加icon组件。将文本组件放于Container组件中来添加上边距,将icon与text分离开。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Widget buttonSection = new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildButtonColumn(Icons.call, 'CALL'),
buildButtonColumn(Icons.near_me, 'ROUTE'),
buildButtonColumn(Icons.share, 'SHARE'),
],
),
);
//...
}
由于文本太长,其实现我们赋值于一个变量。将文本放在Container中,四周边距设置32px。设置softwrap属性,这个属性表示当每行文本遇到句号或者逗号时是否需要换行。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//...
Widget textSection = new Container(
padding: const EdgeInsets.all(32.0),
child: new 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,
),
);
//...
}
目前只剩头部图片部分还未实现。这张图片基于Creative Commons协议在网上是可以获取到的(当然学习过程,可以自己比较随意的拿一张图片进行)。由于图片较大且网络加载慢,所以在Step 0步骤中已经inlude进来并且修改了pubspec.yml文件,可以直接在本地进行访问。
body: new ListView(
children: [
new Image.asset(
'images/lake.jpg',
height: 240.0,
fit: BoxFit.cover,
),
// ...
],
)
BoxFit.cover 告诉Framework图片需要尽可能的小但是需要充满显示部分。
最后一步,将删除个步骤中定义的组件最终整合在一起。所有组件放置于ListView中。
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = new Container(
padding: const EdgeInsets.all(32.0),
child: new Row(
children: [
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new Container(
padding: new EdgeInsets.only(bottom: 8.0),
child: new Text(
'Oeschinec lake Compground',
style: new TextStyle(fontWeight: FontWeight.bold),
),
),
new Text(
'Kandersteg, Switzerland',
style: new TextStyle(color: Colors.grey[500]),
),
],
)),
new Icon(
Icons.star,
color: Colors.red[500],
),
new Text('41')
],
),
);
// button section
Column buildButtonColumn(IconData icon, String label) {
Color color = Theme.of(context).primaryColor;
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
new Icon(icon, color: color),
new Container(
margin: const EdgeInsets.only(top: 8.0),
child: new Text(
label,
style: new TextStyle(
color: color,
fontSize: 12.0,
fontWeight: FontWeight.w400,
),
),
),
],
);
}
Widget buttonSection = new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildButtonColumn(Icons.call, 'CALL'),
buildButtonColumn(Icons.near_me, 'ROUTE'),
buildButtonColumn(Icons.share, 'SHARE'),
],
),
);
Widget textSection = new Container(
padding: const EdgeInsets.all(32.0),
child: new 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,
),
);
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(primarySwatch: Colors.blue),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Top Lakes'),
),
body: new ListView(
children: [
new Image.asset(
'images/lake.jpg',
height: 240.0,
fit: BoxFit.cover,
),
titleSection,
buttonSection,
textSection,
],
),
),
);
}
}