在学习 Flutter 时一定要记住 在Flutter中,几乎所有的东西都是Widget,这句话很重要,今天聊的布局在 Flutter 中也是通过 Widget 组件来实现的,包括对齐、填充布局,字体都是。这似乎与前端开发中通过 CSS 布局有很大的不同,但是问题不大,和标题一样,懂 Vue、React 就懂 Flutter 页面布局!
先来看个官方的例子
移动端 Web 布局通常使用 Flex 可以很简单的实现这个布局:
css
<div style="display: flex; justify-content: space-between;">
<button>Call</button>
<button>Route</button>
<button>Share</button>
</div>
那我们刚才提到,Flutter 布局是通过组件的,那我们直接找到 Flutter 行组件 Row
,并且设置它类似 flex 中justify-content:space-between
这样的属性就行
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('Call'),
),
RaisedButton(
onPressed: () {},
child: Text('Route'),
),
RaisedButton(
onPressed: () {},
child: Text('Share'),
),
],
)
通过对比CSS 和 Flutter 的布局方式,我们会得到几个核心规律:
- CSS 的嵌套关系不需要显示的声明,包裹着就行, Flutter 的嵌套关系需要通过
children
或者child
来声明 - CSS 中布局和样式是添加到 HTML 标签上的,比如给图片设置样式
<img width=xxx>
, 将width
设置在img
标签上。而在Flutter中是将样式设置到组件上比如Icon(Icons.star, color:Colors.red)
将color
设置在 Icon 组件上, 甚至样式都是组件,比如设置Text
的 style 时, 需要通过TextStyle
组件设置 - Flutter 中的布局是通过组件实现的
那检测一下我们的理解,如何写一个居中的 Hello World?
由规律 3 可知: 居中是一个组件,我们找到Center
组件 由规律 1 可知: 需要给Center
加一个child属性来声明嵌套一个 Text
文字组件
Dart
Center(
child: Text('Hello World'),
),
有了基本的布局理解后我们基本可以实现我们想要的任何样式。
盒子布局
在CSS 和 Flutter中,盒子布局是一种常见的布局方式。
在CSS中,每个元素都被视为一个矩形盒子,这个盒子由内容、内边距、边框和外边距组成。这种布局方式允许我们精确地控制每个元素的大小和位置。CSS的盒子模型是网页布局的基础。例如,我们可以使用以下CSS代码来创建一个带有内边距、边框和外边距的盒子:
html
<div style="padding:10px; border:1px solid black; margin:10px;">
This is a box.
</div>
在Flutter中,盒子布局是通过Container
Widget来实现的。Container
Widget可以设置边距、填充、边框和背景颜色等属性,从而创建一个可定制的盒子。以下是在Flutter中创建一个带有内边距、边框和外边距的盒子的示例:
less
Container(
margin: EdgeInsets.all(10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1.0),
),
child: Text('This is a box.'),
)
在这个例子中,我们创建了一个带有10像素外边距、10像素内边距和1像素黑色边框的盒子,盒子中的文字是'This is a box.'。
稍稍扩展一下,在CSS中,我们可以通过使用flex布局来创建更加复杂的盒子布局。例如,我们可以使用以下CSS代码来创建一个水平排列的盒子布局, 它指定了每个盒子的占比。
html
<div style="display: flex;">
<div style="flex: 1; padding:10px; border:1px solid black; margin:10px;">Box 1</div>
<div style="flex: 2; padding:10px; border:1px solid black; margin:10px;">Box 2</div>
<div style="flex: 1; padding:10px; border:1px solid black; margin:10px;">Box 3</div>
</div>
在Flutter中,我们可以通过使用Flex
和Expanded
Widget来创建更加复杂的盒子布局。Flex
Widget可以创建一个线性布局,而Expanded
Widget则可以让这个布局中的元素填充可用空间,填充多少可以通过flex
属性控制,示例:
dart
Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.all(10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1.0),
),
child: Text('Box 1'),
),
),
Expanded(
flex: 2,
child: Container(
margin: EdgeInsets.all(10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1.0),
),
child: Text('Box 2'),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.all(10.0),
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1.0),
),
child: Text('Box 3'),
),
),
],
)
在这个例子中,我们创建了一个水平排列的盒子布局,盒子1和盒子3的宽度是盒子2的宽度的一半。
来做个朋友圈九宫格
效果类似这样(网上截的图。。。)
先来做9宫格图片墙
dart
Row(
children: <Widget>[
Expanded(
child: Container(
margin: EdgeInsets.all(4.0),
child: Image.asset('images/1.png', height: 100, width: 100, fit:BoxFit.cover),
)),
Expanded(
child: Container(
margin: EdgeInsets.all(4.0),
child: Image.asset('images/2.png', height: 100, width: 100, fit:BoxFit.cover),
)),
Expanded(
child: Container(
margin: EdgeInsets.all(4.0),
child: Image.asset('images/3.png', height: 100, width: 100, fit:BoxFit.cover),
)),
],
)
Tips: 使用 Image.asset 之前需要先在 pubspec.yaml
中声明静态资源文件路径
yaml
flutter:
assets:
- images/
显然剩下的 6 张再这么 copy 出来太蠢了,上循环
dart
static const List<String> images = [
'images/1.png',
'images/2.png',
'images/3.png',
'images/4.png',
'images/5.png',
'images/6.png',
'images/7.png',
'images/8.png',
'images/9.png'
];
List<Widget> buildRows() {
List<Widget> rows = [];
for (int i = 0; i < images.length; i += 3) {
rows.add(Row(
children: images
.sublist(i, i + 3)
.map((image) => Expanded(
child: Container(
margin: EdgeInsets.all(4.0),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset(image, fit: BoxFit.cover),
),
),
))
.toList(),
));
}
return rows;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body: Center(child: Column(children: buildRows()))));
}
在这段代码中,我们先创建一个空的rows
列表来存储Row Widget。
然后,通过一个for循环,每次循环从images
列表中取出三个图片路径,然后创建一个Row Widget。在这个Row Widget中,每张图片都被封装在一个Expanded Widget中,这样可以确保每张图片都能在水平方向上平均分配空间。每张图片都被包裹在一个Container Widget中,每个Container都设置了4.0的边距。在每个Container中,图片是通过Image.asset
方法加载的,image
参数是图片的路径,fit: BoxFit.cover
是设置图片的填充模式,保证图片在Container中充满,但是可能会被裁剪。
布局是一个很灵活的东西,不止一种解法,对于这个例子我们也可以使用 GridView
Widget 来实现 9 宫格
dart
home: Scaffold(
body: Center(
child: GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.zero,
crossAxisSpacing: 0,
mainAxisSpacing: 0,
children: List.generate(9, (index) {
int picName = index + 1;
return Center(
child: Image.asset('images/$picName.png',
width: 100, height: 100, fit: BoxFit.cover),
);
})))));
在这个例子中,我们创建了一个GridView
Widget,并设置了crossAxisCount
为 3,这意味着每行会显示三个子Widget。然后,我们使用List.generate
方法来创建9个Image.asset
Widget作为GridView
的子Widget。这样就可以实现每行显示三张图片的布局。
同时将 padding
设置为 EdgeInsets.zero
消除 GridView 的默认间距, crossAxisSpacing
是指网格视图中每列之间的间距, mainAxisSpacing
是指网格视图中每行之间的间距。
限制图片尺寸
我们来看一个诡异的事情
dart
Widget build(BuildContext context) {
return MaterialApp(
// home: Scaffold(body: Center(child:Column(children: buildRows()))) );
home: Scaffold(
body: Center(
child: Row(children: [
Image.asset('images/1.png')
],))));
}
这段代码看起来没什么,但是实际效果竟然:
但如果这个图片不在 Row 中,显示就是正常的
dart
Widget build(BuildContext context) {
return MaterialApp(
// home: Scaffold(body: Center(child:Column(children: buildRows()))) );
home: Scaffold(
body: Center(
child:
Image.asset('images/1.png')
)));
}
原因很简单,就是图片太大导致 Row 溢出了,那这时就有2种解决办法:
- 溢出的话就给他加个横向滚动条,不让他溢出
- 限制图片填充满 Row,不按照原来图片尺寸
那我们先按照方法1来处理
less
@override
Widget build(BuildContext context) {
return MaterialApp(
// home: Scaffold(body: Center(child:Column(children: buildRows()))) );
home: Scaffold(
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
Image.asset('images/1.png')
],
))
));
}
}
我们将 SingleChildScrollView 的 scrollDirection
属性设置为Axis.horizontal
,使得其子组件 Row 可以在水平方向上滚动来避免溢出
接着我们再使用方法2来处理,其实方法 2 我们之前已经用过了,就是使用 Expanded
组件来让图片填充满这个 Row, fit
属性是指填充时该如何适应比如裁切(BoxFit.cover)
dart
Widget build(BuildContext context) {
return MaterialApp(
// home: Scaffold(body: Center(child:Column(children: buildRows()))) );
home: Scaffold(
body: Row(
children: <Widget>[
Expanded(child: Image.asset('images/1.png', fit: BoxFit.cover))
],
)));
}