1. Container 简介
Container
在 Flutter
中扮演着容器的角色, 是页面布局中的重要角色, 通常是作为 父Widget
包在其他 Widget
的外层来做一系列的 UI 相关操作, 下面这几项是本文探索的重点. 还有部分属性和功能没有列出, 后续如果用到了, 会给大家补上, 谢谢理解.
1.1 本文重点
- 设置
背景色
, 为那些没有背景色的Widget
提供帮助; - 设置
宽高
, 可以起到一定的占位作用; - 设置
child
的对齐方式. - 设置四周的
内
外
边距为其他Widget
调整布局位置; - 设置
背景装饰器
, 主要有背景图, 背景色, 背景圆角, 背景边框; 以及设置裁切器
超出背景装饰范围外的内容. 这两个属性是相关的就放在一起来讲解; - 设置
前景装饰器
, 主要有前景图, 前景色, 前景圆角, 前景边框;
为了美观, 我们要验证的
Container
放在body
的中心显示. 即给Container
包一层Center
. 核心代码都在Widget testContainer()
函数中. 使用iphone8
模拟器将项目跑起来.
2. Container 常用属性
注意
为了减少图片占用的空间, 除了2.1 背景颜色
截图是完整的, 后边的截图如果没有必要就只截 Container
这部分, 就不截取整个模拟器了, 代码也只提供变化的这部分, 即Widget testContainer()
函数, 谢谢理解.
2.1 color - 背景颜色
color
是背景颜色属性, 不能与背景装饰属性 decoration
共存, 在构造函数中有 assert(color == null || decoration == null
这样一个断言, 二者不能同时存在, 否则会报错.
SizedBox
起一个占位作用.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: const Text('Container Demo'),),
body: Center(
child: testContainer(),
),
),
);
}
Widget testContainer() {
return Container(
color: Colors.yellow,
child: const SizedBox(width: 200, height: 300,),
);
}
}
2.2 child
child
属性是 Widget
类型, 用来接收需要包装的 Widget
, 可以是文本, 可以是图片 ... , 是最重要的属性, 此处我们设置一张图片, 图片大小是 200 * 300 px
, 设置背景色为黄色, 分别截取效果图进行对比.
结果:
- 图片无论是否设置宽高, 都看不到背景色, 能看到的只有图片;
- 没有设置图片宽高的是按图片自己宽高展示, 设置了宽高的会以设置的具体宽高展示, 明显比原图小了.
以上两点可以看出 Container
默认宽高是和 child
宽高相同.
Widget testContainer() {
return Container(
color: Colors.yellow,
child: Image.asset(
'images/icon_img.png', //200 * 300
// width: 100,
// height: 150,
),
);
}
-
child 不设置宽高
-
child 设置宽高
2.3 width 和 height
2.3.1 Container 的宽高都设置
2.3.1.1 Container 的宽高大于等于 child 宽高
width
和 height
都设置, 分别大于或者等于
图片的宽和高, 设置 Container
的宽
* 高
分别为 250
* 400
, child
大小不设置, 此时宽
高
都是大于图片大小的, 此时 Container
的大小是比图片大的, 从效果图可以看到图片实际大小
显示, 多余的空间就可以直到占位或者间距的作用.
Widget testContainer() {
return Container(
color: Colors.yellow,
width: 250,
height: 300,
child: Image.asset(
'images/icon_img.png',
),
);
}
2.3.1.2 Container 的宽高不
全大于等于 child 宽高
width
和 height
都设置, 期中一个小于
图片的或者两个都小于
图片的, 图片的宽或者高
以变化率(即缩小倍数
)大的那个为准, 把空间充满, 然后按图片宽高比例计算高或者宽
. 这样可以保证图片等比缩小
显示. 如 3
所示, 宽的变化率为 200/80 = 2.5
, 高的变化率为 300/100=3
, Container
的高度就是图片高度, 宽度为200/3
.
-
width
小于图片的Widget testContainer() { return Container( color: Colors.yellow, width: 80, height: 400, child: Image.asset( 'images/icon_img.png', ), ); }
-
height 小于图片的
Widget testContainer() { return Container( color: Colors.yellow, width: 250, height: 80, child: Image.asset( 'images/icon_img.png', ), ); }
-
width 和 height 都小于图片的
Widget testContainer() { return Container( color: Colors.yellow, width: 80, height: 100, child: Image.asset( 'images/icon_img.png', ), ); }
2.3.2 Container 的宽高只设置期中一个
-
当
child
是图片这种有具体宽高的组件时, 如果 Container 的宽高只设置期中一个, 则另一个的值会根据child
的宽高比例计算得出, 如果是大于图片对应的宽高, 则图片原宽高居中显示, 此时Container
留有空白. -
当
child
是文本这样根据宽度自动填充的组件时, 默认从左到右的展示方向, 如果只设置了宽度, 那么会在一行占满之后自动换行, 直到充满Container
所有可以占用的空间. 如果只设置了高度, 首先会把Container
横向撑开, 到最大宽度还不够时, 就会换行, 如果高度不足, 那就有可能导致文本显示不全.
2.3.2.1 只设置宽
-
图片, 分别设置小于图片宽度的 100 和大于图片宽度的 300,
Widget testContainer() { return Container( color: Colors.yellow, width: 100, // 300 child: Image.asset( 'images/icon_img.png', ), ); }
-
宽 = 100
-
宽 = 300
-
-
文本
Widget testContainer() { return Container( color: Colors.yellow, width: 300, child: const Text( '静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡', style: TextStyle(fontSize: 20,), ), ); }
2.3.2.2 只设置高, 分别设置小于图片高度的 150 和大于图片宽度的 400,
-
图片
Widget testContainer() { return Container( color: Colors.yellow, height: 150, // 400 child: Image.asset( 'images/icon_img.png', ), ); }
-
高 = 150
-
高 = 400
-
-
文本
Widget testContainer() { return Container( color: Colors.yellow, height: 45, child: const Text( '静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡', style: TextStyle(fontSize: 20,), ), ); }
2.4 alignment - 对齐方式
用于控制 child
在 Container
内部的位置. Flutter
的坐标原点在区域中间, 左边为负坐标, 右边为正坐标, 最左边和最上边是 -1
, 最右边和最下边是 1
, Alignment
这个类内置了9种静态位置, 是可以直接用的, 除此之外可以用构造函数自己根据实际情况去构造一个, 如 Alignment(-1, -1)
.
-
Flutter 坐标系, 各位置对应的
(x, y)
-
内置了
9种
静态位置变量 以及简单的示意图/// The top left corner. static const Alignment topLeft = Alignment(-1.0, -1.0); /// The center point along the top edge. static const Alignment topCenter = Alignment(0.0, -1.0); /// The top right corner. static const Alignment topRight = Alignment(1.0, -1.0); /// The center point along the left edge. static const Alignment centerLeft = Alignment(-1.0, 0.0); /// The center point, both horizontally and vertically. static const Alignment center = Alignment(0.0, 0.0); /// The center point along the right edge. static const Alignment centerRight = Alignment(1.0, 0.0); /// The bottom left corner. static const Alignment bottomLeft = Alignment(-1.0, 1.0); /// The center point along the bottom edge. static const Alignment bottomCenter = Alignment(0.0, 1.0); /// The bottom right corner. static const Alignment bottomRight = Alignment(1.0, 1.0);
- 坐标位置示意图
Widget testContainer() { return Container( color: Colors.yellow, alignment: const Alignment(-1, -1), // 相当于 topLeft width: 300, height: 400, child: Image.asset( 'images/icon_img.png', //200 * 300 ), ); }
Alignment
构造函数Alignment(this.x, this.y)
x
,y
的取值范围 [-1, 1],double
类型.
2.5 padding - 内边距
内边距就是 child
四边到 Container
四边的距离. EdgeInsets
类型. 下面我们分两种情况去验证, 一是 没有设置 Container
的宽高, 二是 设置 Container
的宽高,
-
设置内边距
左上右下
依次为10, 20, 40, 80
, 没有设置Container
的宽高, 即Container
的大小是自动的, 此时的Container
宽是图片的宽+左右边距
, 高是图片的高+上下边距
.Widget testContainer() { return Container( color: Colors.yellow, padding: const EdgeInsets.fromLTRB(10, 20, 40, 80), child: Image.asset( 'images/icon_img.png', ), ); }
-
当我们不设置
padding
的时候, 只设置宽高, 图片是居中显示的, 即左右边距相同, 上下边距相同, 这个我们在width 和 height
这一部分已经知道了. 现在我们同时设置padding
和 宽高. 那么最终用于显示的区域就是减去内边距剩余的部分. 剩余的部分再按照width 和 height
这一部分的规则去处理, 如果左右边距之和大于等于宽或者上下边距之和大于等于高,那么图片将不会显示出来.Widget testContainer() { return Container( color: Colors.yellow, width: 300, height: 400, padding: const EdgeInsets.fromLTRB(50, 50, 0, 0),//内边距 child: Image.asset( 'images/icon_img.png', //200 * 300 ), ); }
注意
我们按照代码的设置的边距, 右和下为什么不是0
呢, 我们来计算一下, 可用于显示的区域的宽高分别用 sw
和 sh
表示, 最终 sw = 250 , sh = 350
, 我们把这个区域暂时称作 可显示区域
, 比图片宽高都大, 在可显示区域水平和垂直方向各多出50
的距离. 根据 width 和 height
这一部分图片会居中显示, 所以各边距再 + 25, 就是最终的边距. 如果有 alignment
这种影响边距的设置, 则只会在 可显示区域
生效, 可以自己体验一下.
cw = 300
ch = 400
imgw = 200
imgh = 300
pl = 50
pr = 0
pt = 50
pb = 0
sw = cw - pl - pr = 250
sh = ch - pt - pb = 350
spl = spr = (sw - imgw) / 2 = 25
spt = spb = (sh - imgh) / 2 = 25
总边距
totalpl = pl + spl = 75
totalpr = spr = 25
totalpt = pt + spt = 75
totalpb = spb = 25
2.6 margin - 外边距
为了能清楚看到外边距, 在外层再包一个 Container
, 背景色设置为红色, 设置黄色的外边距都为 40, 内边距为 10, 效果如下图, 效果与 padding
相呼应, 一个管外边距, 一个管内边距.
Widget testContainer() {
return Container(
color: Colors.blue,
child: Container(
color: Colors.yellow,
padding: const EdgeInsets.all(10),//内边距
margin: const EdgeInsets.all(30),//外边距
child: Image.asset(
'images/icon_img.png', //200 * 300
),
),
);
}
2.7 decoration 和 clipBehavior
decoration
和 clipBehavior
裁切这两个属性是相关的就放在一起来讲解, 构造函数中有两个与 decoration
相关的断言个.
默认
clipBehavior == Clip.none
, 不裁切.
assert(decoration != null || clipBehavior == Clip.none)
- 如果设置
clipBehavior != Clip.none
那么必须要设置decoration
. - 如果设置
clipBehavior == Clip.none
那么decoration
有可无,
实际上
clipBehavior
裁切的是背景装饰器.- 如果设置
assert(color == null || decoration == null
decoration
与color
不能同时存在, 因为decoration
本身也有color
属性, 而且又是背景装饰, 如果都设置会显得有点冗余.
decoration
是 Decoration
类型, 是一个背景装饰属性, 比如常用的 背景色
圆角
, 边框
, 背景图
, 所有装饰效果都在 child
下层, 即会被 child
盖住, 比如文本飘浮在背景图上面, 就可以用这个属性. 此处使用一个子类BoxDecoration
来做演示, 当然还有其他一些子类, 功能不同.
可以设置很多样式, 我只探索了圆角, 边框, 背景图这几个样式, 其他的大家可以我尝试一下.
2.7.1 color - 背景色
此处只使用了 decoration
的 color
属性, 与 Container
的 color
效果相同. 为了美观设置了内边距和宽度, 让高度自适应.
注意:
Container
的color
和decoration
不能同时设置. 否则会报错
Widget testContainer() {
return Container(
// color: Colors.yellow, // 与 decoration 不能同时存在
padding: const EdgeInsets.all(5),
width: 300,
decoration: const BoxDecoration(
color: Colors.yellow,
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20,),
),
);
}
2.7.2 borderRadius - 背景圆角 / 椭圆角
为了突出效果, 设置圆角半径稍微大一些, 平时展示也不会有这么大的圆角, 我们发现文字和图片显示在了黄色区域以外的地方, 这就是背景装饰, 只是改变了背景部分, 并没有改变 Container
的显示区域,
2.7.2.1 文本
此处因为是文本组件, 解决这个问题我们只能设置内边距, 进行裁切会造成显示不全, 显然是不合适的.
- 优化前的效果
Widget testContainer() { return Container( margin: const EdgeInsets.all(40), height: 200, decoration: const BoxDecoration( color: Colors.yellow, borderRadius: BorderRadius.all(Radius.circular(50)), ), child: const Text( '静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡', style: TextStyle(fontSize: 20,), ), ); }
- 优化后的效果
Widget testContainer() { return Container( padding: const EdgeInsets.all(10), margin: const EdgeInsets.all(40), height: 200, decoration: const BoxDecoration( color: Colors.yellow, borderRadius: BorderRadius.all(Radius.circular(5)), ), child: const Text( '静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡', style: TextStyle(fontSize: 20,), ), ); }
2.7.2.2 图片
对于图片, 我们经常会有裁切圆角这种需求, 所以可以根据自己的实际情况, 选择裁切, 或者设置内边距, 或者都使用. 此处我们直接进行裁切, 使用内边距解决问题时候, 要注意背景色是否能满足条件.
为的看出效果需要设置一个内边距, 否则看不出已经切了圆角.
- 优化前的效果
Widget testContainer() { return Container( padding: const EdgeInsets.all(10), decoration: const BoxDecoration( color: Colors.yellow, borderRadius: BorderRadius.all(Radius.circular(50)), ), child: Image.asset( 'images/icon_img.png', //200 * 300 ), ); }
- 优化后的效果, 已经去掉内边距, 完美切出圆角.
Widget testContainer() { return Container( padding: const EdgeInsets.all(10), clipBehavior: Clip.hardEdge, decoration: const BoxDecoration( color: Colors.yellow, borderRadius: BorderRadius.all(Radius.circular(50)), ), child: Image.asset( 'images/icon_img.png', //200 * 300 ), ); }
2.7.2.3 椭圆角
椭圆定义
const Radius.elliptical(this.x, this.y);
椭圆角
的设置需要给出长半轴和短半轴, 其他和圆角一样, 只是弧度不一样而已, 所以只给出一个代码和效果图, 就不再一一列举存在的问题和解决办法. 如果设置的值为特殊值 elliptical(100, 150)
, x
和y
分别等于宽高的一半. 那么将是一个椭圆, 如果小于则两头分别是半个椭圆.
Widget testContainer() {
return Container(
padding: const EdgeInsets.all(10),
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.all(Radius.elliptical(80, 100)),
),
child: Image.asset(
'images/icon_img.png', //200 * 300
),
);
}
- 特殊值
elliptical(100, 150)
2.7.3 image 背景图
我们设置一个背景图, 让文字飘浮于背景图上方, 再设置一些边距, 给个小圆角, 效果简直是完美.
图片填充模式 BoxFit
, 是一个枚举, 背景图的填充模式用 fill
设置为铺满, 其他的大家可以试试. 因为不是本文重点就不展开了.
enum BoxFit {
fill,
contain,
cover,
fitWidth,
fitHeight,
none,
scaleDown,
}
Widget testContainer() {
return Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.all(40),
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
image: DecorationImage(
image: AssetImage('images/icon_img.png'),
fit: BoxFit.fill
),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20, color: Colors.deepPurple),
),
);
}
2.7.1 border 边框
把边框设置为10, 看上去更明显一些. 此处用文本来验证, 让文本自动把 Container
撑开到最大宽度, 从中我们可以发现, 边框从 Container
向内部延伸的. 剩余的区域用于显示.
Widget testContainer() {
return Container(
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(width: 10, color: Colors.blue),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20, color: Colors.deepPurple),
),
);
}
2.8 foregroundDecoration - 前景装饰器
foregroundDecoration
是一个前景装饰属性, 所有装饰效果都飘浮在 child
上方, 即会盖住 child
, 与 decoration
类型相同, 但是不受 color
和 clipBehavior
的影响.
2.8.1 color - 前景色
前景色设置一个透明的, 这样不至于的把所有内容都盖住, child
用文本, 很明显这个颜色是飘浮在文本的上方的.
Widget testContainer() {
return Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.all(40),
foregroundDecoration: const BoxDecoration(
color: Color.fromARGB(120, 0, 255, 0),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20,),
),
);
}
2.8.2 borderRadius - 前景圆角
设置前景色, 才能看出效果, 这个很明显是没有完全盖住, 解决方案参照圆角的.
Widget testContainer() {
return Container(
margin: const EdgeInsets.all(40),
foregroundDecoration: const BoxDecoration(
color: Color.fromARGB(120, 0, 255, 0),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20,),
),
);
}
2.8.3 image - 前景图
为了能看到效果好点儿, 还是需要来一个圆角和背景色, 具体使用根据自己的需求灵活运用.
Widget testContainer() {
return Container(
color: Colors.yellow,
margin: const EdgeInsets.all(40),
foregroundDecoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(50)),
image: const DecorationImage(
image: AssetImage('images/icon_img.png'),
fit: BoxFit.fill
),
// border: Border.all(width: 5, color: Colors.green),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20,),
),
);
}
2.8.4 border - 前景边框
前景边框也是从 Container
的周边向内延伸的, 但是会盖住 child
, 一般设置个内边距就可以解决问题, 比较简单, 就不展示了, 可以自己试试.
Widget testContainer() {
return Container(
color: Colors.yellow,
// padding: const EdgeInsets.all(10),
margin: const EdgeInsets.all(40),
foregroundDecoration: BoxDecoration(
border: Border.all(width: 5, color: Colors.blue),
),
child: const Text(
'静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡.静夜思 * 李白 • 床前明月光, 疑是地上霜, 举头望明月, 低头思故乡',
style: TextStyle(fontSize: 20,),
),
);
}
3 综合应用
3.1 给图片child
设置带圆角和边框
需求
Container
的 child
是一张图片时, 要求设置成 圆角
和 边框
, 并不显示, 图片不能显示到圆角之外.
分析
如果使用 decoration
和 clipBehavior
设置圆角, 边框, 裁切, 那么圆角部分的边框会被图片盖住.
如果只使用 foregroundDecoration
设置圆角和边框, 只会飘浮在图层上方一个圆角边框.
综合以上两点, 我们可以使用 decoration
和 clipBehavior
设置圆角, 裁切, 然后用 foregroundDecoration
设置圆角边框, 让边框飘浮于上方, 保持前后圆角一致即可, 当然这会盖住图片一个边框的部分, 再加上边框宽度的内边距, 那就完美了. 不过 Container
就会变大了一点点, 如果不希望他变大, 就设置他的宽高, 让图片变小一点点.
正确做法
- 首先使用
decoration
和clipBehavior
将Container
的四个角设置成圆角并切掉圆角以外的部分. - 然后再使用
foregroundDecoration
设置圆角 和 边框, 圆角半径前后相同. - 设置一个内边距, 边距与边框的宽度相同, 保证完整展示图片, 否则会被边框盖住一圈.
Widget testContainer() {
return Container(
padding: const EdgeInsets.all(5),
margin: const EdgeInsets.all(40),
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(50)),
),
foregroundDecoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(50)),
border: Border.all(width: 5, color: Colors.blue),
),
child: Image.asset(
'images/icon_img.png', //200 * 300
),
);
}
4 重点总结
width
和height
的灵活运用, 在屏幕适配上会起到很大作用.alignment
对齐方式, 图片默认居中, 文本默认左上角, 另外可以自己根据需求构造.padding
和margin
灵活搭配使用, 在屏幕适配上会起到很大作用.decoration
和foregroundDecoration
对Container
进行样式的装饰.
能看到这里的, 我相信你肯定是个有耐心的人, 写作不易. 麻烦动动你可爱的小手点个赞👍🏻, 鼓励一下. 你的鼓励就是我写作的力量源泉😄😄😄, 万分感谢 !!!
如果哪里写的有问题, 或者是不完美, 欢迎评论区留言, 我一定加以改正, 谢谢 !!!.