持续创作,加快成长!这是我参加「日新计划 6 月更文挑战」的第1天,点击检查活动详情
你是否需求了解 Flutter
布局的事例?
这里我将展现我在运用 Flutter
布局的代码片段。我将经过精美的代码片段结合可视化的图形来举例。
本文注重 Flutter
部件中比较有用的一些来展现,而不是蜻蜓点水展现一大推的部件内容。
本文是翻译的文章,选用意译的方式
Row and Column
行(Row)和列(Column)的布局
MainAxisAlignment
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
CrossAxisAlignment
假如你需求文本是针对基线对齐,那么你应该运用 CrossAxisAlignment.baseline
。
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
Text(
'Baseline',
style: Theme.of(context).textTheme.headline2,
),
Text(
'Baseline',
style: Theme.of(context).textTheme.bodyText2,
),
],
),
Row | Column |
---|---|
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
MainAxisSize
Row | Column |
---|---|
Row /*or Column*/(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
Row | Column |
---|---|
Row /*or Column*/(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
IntrinsicWidth and IntrinsicHeight
在行列布局中,怎么使得一切的部件跟宽度/高度最大的部件同宽/同高呢?如下:
咱们假有下面的布局:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth')),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('Short'),
),
RaisedButton(
onPressed: () {},
child: Text('A bit Longer'),
),
RaisedButton(
onPressed: () {},
child: Text('The Longest text button'),
),
],
),
),
);
}
那么,你想一切的按钮的宽度都跟最宽的按钮那么宽,那就运用 IntrinsicWidth
:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth')),
body: Center(
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('Short'),
),
RaisedButton(
onPressed: () {},
child: Text('A bit Longer'),
),
RaisedButton(
onPressed: () {},
child: Text('The Longest text button'),
),
],
),
),
),
);
}
同理,假如你想一切的部件的高度跟最高的部件一样高,你需求结合 IntrinsicHeight
和 Row
来完成。
Stack
Stack
很合适小部件相互叠加。
Widget build(BuildContext context) {
Widget main = Scaffold(
appBar: AppBar(title: Text('Stack')),
);
return Stack(
fit: StackFit.expand,
children: <Widget>[
main,
Banner(
message: "Top Start",
location: BannerLocation.topStart,
),
Banner(
message: "Top End",
location: BannerLocation.topEnd,
),
Banner(
message: "Bottom Start",
location: BannerLocation.bottomStart,
),
Banner(
message: "Bottom End",
location: BannerLocation.bottomEnd,
),
],
);
}
运用自己的部件,你需求将它们放在 Positioned
部件中。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stack')),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Material(color: Colors.yellowAccent),
Positioned(
top: 0,
left: 0,
child: Icon(Icons.star, size: 50),
),
Positioned(
top: 340,
left: 250,
child: Icon(Icons.call, size: 50),
),
],
),
);
}
假如你不想猜想顶部/底部的值,你能够运用 LayoutBuilder
部件来检索它们的值。
Widget build(BuildContext context) {
const iconSize = 50;
return Scaffold(
appBar: AppBar(title: Text('Stack with LayoutBuilder')),
body: LayoutBuilder(
builder: (context, constraints) =>
Stack(
fit: StackFit.expand,
children: <Widget>[
Material(color: Colors.yellowAccent),
Positioned(
top: 0,
child: Icon(Icons.star, size: iconSize),
),
Positioned(
top: constraints.maxHeight - iconSize,
left: constraints.maxWidth - iconSize,
child: Icon(Icons.call, size: iconSize),
),
],
),
),
);
}
Expanded
Expanded
合作 Flex\Flexbox 布局完成,它对于多项目分配空间很棒。
Row(
children: <Widget>[
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.red),
),
flex: 3,
),
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.green),
),
flex: 2,
),
Expanded(
child: Container(
decoration: const BoxDecoration(color: Colors.blue),
),
flex: 1,
),
],
),
ConstrainedBox
默许的,很多部件多尽量运用小空间,比方:
Card(
child: const Text('Hello World!'),
color: Colors.yellow,
),
ConstrainedBox
允许小部件根据需求运用剩下的空间。
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: const Card(
child: const Text('Hello World!'),
color: Colors.yellow,
),
),
运用 BoxConstraints
,你能够指定一个小部件能够有多少空间,你能够指定高度/宽度的最小/最大值。
除非指定值,不然 BoxConstraints.expand
运用无限的空间量(也就是运用剩下的一切空间):
ConstrainedBox(
constraints: BoxConstraints.expand(height: 300),
child: const Card(
child: const Text('Hello World!'),
color: Colors.yellow,
),
),
上面的写法等同下面的写法:
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity,
maxWidth: double.infinity,
minHeight: 300,
maxHeight: 300,
),
child: const Card(
child: const Text('Hello World!'),
color: Colors.yellow,
),
),
Align
有时候,咱们很难设置咱们的小部件到正确的巨细 — 比方,它们自在扩展,可是这不是你想要的。
当你在 Column
中运用 CrossAxisAlignment.stretch
的时候,上面的现象就会产生,而你想要的是这个按钮不扩展。
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Align(
child: RaisedButton(
onPressed: () {},
child: const Text('Button'),
),
),
],
),
当你的小部件并不受限你设定的约束时,那么你能够测验运用 Align
部件包裹它。
Container
Container
是最常用的部件之一 — 有如下的好处:
Container as a layout tool
当你没有指定 Container
的高度 height
或许宽度 width
的时候,它会自动适配 child
子部件的巨细。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container as a layout')),
body: Container(
color: Colors.yellowAccent,
child: Text("Hi"),
),
);
}
假如你想扩展 Container
来适配它的父部件,请为特点高度 height
或宽度 width
设定值 double.infinity
。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container as a layout')),
body: Container(
height: double.infinity,
width: double.infinity,
color: Colors.yellowAccent,
child: Text("Hi"),
),
);
}
Container as decoration
你能够运用 Container
的 color
特点来更改其布景色彩,可是你也能够运用 decoration
和 foregroundDecoration
来更改。(运用这两个特点,你完全能够更改 Container
的姿态,这个咱们迟点说)。
decoration
总是在 child
特点的后边,而 foregroundDecoration
总是在 child
特点的后边。(这也不一定)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container.decoration')),
body: Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(color: Colors.yellowAccent),
child: Text("Hi"),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container.foregroundDecoration')),
body: Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(color: Colors.yellowAccent),
foregroundDecoration: BoxDecoration(
color: Colors.red.withOpacity(0.5),
),
child: Text("Hi"),
),
);
}
Container as Transform
假如你不想运用 Transform
部件来更改布局,你能够直接运用 Container
中的 transform
特点。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container.transform')),
body: Container(
height: 300,
width: 300,
transform: Matrix4.rotationZ(pi / 4),
decoration: BoxDecoration(color: Colors.yellowAccent),
child: Text(
"Hi",
textAlign: TextAlign.center,
),
),
);
}
BoxDecoration
decoration
一般用于更改 Container
部件的外观。
image: DecorationImage
图片作为布景:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('image: DecorationImage')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
fit: BoxFit.fitWidth,
image: NetworkImage(
'', // 地址现已无效
),
),
),
),
),
);
}
border: Border
指定 Container
的边框看起来该怎样。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('border: Border')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(color: Colors.black, width: 3),
),
),
),
);
}
borderRadius: BorderRadius
使得边框角变圆。
假如装修中 shape
特点的值是 BoxShape.circle
,那么 borderRadius
不会起作用。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('borderRadius: BorderRadius')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(color: Colors.black, width: 3),
borderRadius: BorderRadius.all(Radius.circular(18)),
),
),
),
);
}
shape: BoxShape
BoxDecoration
能够是矩形/正方形或许椭圆/圆形。
对于其他形状,你能够运用 ShapeDecoration
代替 BoxDecoration
。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('shape: BoxShape')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
shape: BoxShape.circle,
),
),
),
);
}
boxShadow: List
为 Container
增加阴影。
这个参数是一个列表,你能够指定多个不同的阴影并将它们兼并在一同。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('boxShadow: List<BoxShadow>')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.yellow,
boxShadow: const [
BoxShadow(blurRadius: 10),
],
),
),
),
);
}
gradient
有三种类型的突变:LinearGradient
,RadialGradient
和 SweepGradient
。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('gradient: LinearGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [
Colors.red,
Colors.blue,
],
),
),
),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('gradient: RadialGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: RadialGradient(
colors: const [Colors.yellow, Colors.blue],
stops: const [0.4, 1.0],
),
),
),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('gradient: SweepGradient')),
body: Center(
child: Container(
height: 200,
width: 200,
decoration: BoxDecoration(
gradient: SweepGradient(
colors: const [
Colors.blue,
Colors.green,
Colors.yellow,
Colors.red,
Colors.blue,
],
stops: const [0.0, 0.25, 0.5, 0.75, 1.0],
),
),
),
),
);
}
backgroundBlendMode
backgroundBlendMode
是 BoxDecoration
中最杂乱的特点之一。
它负责将 BoxDecoration
中色彩/突变,以及 BoxDecoration
上的任何内容混合一同。
运用 backgroundBlendMode
, 你能够运用 BlendMode
枚举中指定的一长串算法。
首先,让咱们将 BoxDecoration
设置为 foregroundDecoration
,它被制作在 Container
子部件之上(而 decoration
会制作在子部件之后)。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('backgroundBlendMode')),
body: Center(
child: Container(
height: 200,
width: 200,
foregroundDecoration: BoxDecoration(
backgroundBlendMode: BlendMode.exclusion,
gradient: LinearGradient(
colors: const [
Colors.red,
Colors.blue,
],
),
),
child: Image.network(
'', // 图片 404
),
),
),
);
}
backgroundBlendMode
不仅仅影响它地点的 Container
。
backgroundBlendMode
改变其地点 Container
及其一下部件树的内容的色彩。
下面的代码又一个父部件 Container
来制作一个 image
,然后有一个子部件 Container
来运用 backgroundBlendMode
,可是你还是获取到和之前的作用。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('backgroundBlendMode')),
body: Center(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'', // 404
),
),
),
child: Container(
height: 200,
width: 200,
foregroundDecoration: BoxDecoration(
backgroundBlendMode: BlendMode.exclusion,
gradient: LinearGradient(
colors: const [
Colors.red,
Colors.blue,
],
),
),
),
),
),
);
}
Material
有切角的边框。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('shape: BeveledRectangleBorder')),
body: Center(
child: Material(
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
side: BorderSide(color: Colors.black, width: 4),
),
color: Colors.yellow,
child: Container(
height: 200,
width: 200,
),
),
),
);
}
Slivers
SliverFillRemaining
即便没有满足的空间,当你想要将内容居中,这个部件也是不可代替的。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverFillRemaining')),
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
FlutterLogo(size: 200),
Text(
'This is some longest text that should be centered'
'together with the logo',
textAlign: TextAlign.center,
),
],
),
),
],
),
);
}
假如居中的内容没有满足的空间,SliverFillRemaining
将变为可翻滚。
假如没运用 SliverFillRemaining
,内容将会像下面这样溢出:
Filling the remaining space
除了对内容居中有用之外,SliverFillRemaining
还会填充剩下视口的可用空间。为此,此部件有必要放置在 CustomScrollView
中,并且有必要是最终一个 sliver
。
假如没有满足的空间,部件将变为可翻滚。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SliverFillRemaining')),
body: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate(const [
ListTile(title: Text('First item')),
ListTile(title: Text('Second item')),
ListTile(title: Text('Third item')),
ListTile(title: Text('Fourth item')),
]),
),
SliverFillRemaining(
hasScrollBody: false,
child: Container(
color: Colors.yellowAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
FlutterLogo(size: 200),
Text(
'This is some longest text that should be centered'
'together with the logo',
textAlign: TextAlign.center,
),
],
),
),
),
],
),
);
}
SizedBox
SizedBox
是最简单可是最常用的小部件之一。
SizedBox as ConstrainedBox
SizedBox
工作方式跟 ConstrainedBox
有些类似。
SizedBox.expand(
child: Card(
child: Text('Hello World!'),
color: Colors.yellowAccent,
),
),
SizedBox as padding
当需求增加内边距和外边距的时候,你能够选择 Padding
或 Container
小部件。可是,它们能够比增加 Sizedbox
更冗长且可读性更低。
Column(
children: <Widget>[
Icon(Icons.star, size: 50),
const SizedBox(height: 100),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
),
SizedBox as an Invisible Object
很多时候,你想经过设置一个 bool
值来隐藏/展现小部件。
Show | Hide |
---|---|
Widget build(BuildContext context) {
bool isVisible = true; // true or false
return Scaffold(
appBar: AppBar(
title: Text('isVisible = $isVisible'),
),
body: isVisible
? Icon(Icons.star, size: 150)
: const SizedBox(),
);
}
由于 SizedBox
有一个 const
构造函数,所以运用 const SizeBox()
真的很便宜。一种更便宜的解决方案是运用 Opacity
小部件,将其 opacity
值更改为 0.0
。这种解决方案的缺点是给定的小部件仅仅不可见,可是还是占用空间。
SafeArea
在不同的平台,有些特定的区域,比方安卓的状态栏或许 iPhone X
的刘海区块,咱们不应该在其下面制作内容。
解决方案是运用 SafeArea
小部件。(下面截图是没运用/运用 SafeArea
)
without | with |
---|---|
Widget build(BuildContext context) {
return Material(
color: Colors.blue,
child: SafeArea(
child: SizedBox.expand(
child: Card(color: Colors.yellowAccent),
),
),
);
}
本文翻译自 Flutter Layout Cheat Sheet。代码现已验证,需求留心 RaisedButton
现已被 ElevatedButton
代替,在现实运用中需求留心。本文重点是其布局思路和技巧。