本文会列举一组 Flutter 布局代码示例。因为 Flutter 相对于 Android 原生的 layout 布局或者是 Compose 布局,还是不太一样。如果不了解到全部的布局方式,在一些应用场景,就不能选择到最佳的布局方式来实现当前的需求。
目录:
- Row and Column
- IntrinsicWidth and IntrinsicHeight
- Stack
- Expanded
- ConstrainedBox
- Align
- Container
decoration: BoxDecoration
• image: DecorationImage
• border: Border
• borderRadius: BorderRadius
• shape: BoxShape
• boxShadow: List<BoxShadow>
• gradient: RadialGradient
• backgroundBlendMode: BlendMode - Material
• shape: BeveledRectangleBorder - Slivers
• SliverFillRemaining - SizedBox
- SafeArea
Row and Column
MainAxisAlignment
void main() => runApp(const MaterialApp(home: TestDemo1()));
class TestDemo1 extends StatelessWidget {
const TestDemo1({super.key});
@override
Widget build(context) => Scaffold(
appBar: AppBar(
title: const Text('TestDemo1')
),
body: Container(
color: Colors.pink,
width: double.infinity,
height: 300,
child: const Row(
mainAxisAlignment: MainAxisAlignment.start, <-- 替换此处
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 50),
],
)
),
);
}
可替换这个 mainAxisAlignment
的设置:
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisAlignment: MainAxisAlignment.spaceAround,
如果需要对齐不同文本的基线,则应使用 CrossAxisAlignment.baseline
。
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
Text(
'Baseline',
style: Theme.of(context).textTheme.display3,
),
Text(
'Baseline',
style: Theme.of(context).textTheme.body1,
),
],
),
CrossAxisAlignment
Row /*or Column*/(
crossAxisAlignment: CrossAxisAlignment.start, <-- 替换此处
children: <Widget>[
Icon(Icons.star, size: 50),
Icon(Icons.star, size: 200),
Icon(Icons.star, size: 50),
],
),
可替换这个 crossAxisAlignment
的设置:
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
mainAxisSize: MainAxisSize.min,
IntrinsicWidth and IntrinsicHeight
希望行或列中的所有小部件与最高/最宽的小部件一样高/宽?
如果你有这种布局:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('IntrinsicWidth')),
body: Center(
child: Column(
children: <Widget>[
ElevatedButton(
onPressed: () {},
child: Text('Short'),
),
ElevatedButton(
onPressed: () {},
child: Text('A bit Longer'),
),
ElevatedButton(
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>[
ElevatedButton(
onPressed: () {},
child: Text('Short'),
),
ElevatedButton(
onPressed: () {},
child: Text('A bit Longer'),
),
ElevatedButton(
onPressed: () {},
child: Text('The Longest text button'),
),
],
),
),
),
);
}
如果您遇到类似的问题,但希望所有小部件与最高的小部件一样高,只需使用 IntrinsicHeight
和 Row
小部件的组合即可。
Stack
非常适合将小部件相互叠加
@override
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,
),
],
);
}
使用你自己的 Widget
,你需要将它们放置在 Positioned Widget
中
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: 50),
),
Positioned(
top: constraints.maxHeight - iconSize,
left: constraints.maxWidth - iconSize,
child: Icon(Icons.call, size: 50),
),
],
),
),
);
}
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
有时你很难将我们的小部件设置为适当的大小 - 例如,当你不想时,它会不断拉伸:
例如,当你有一个带有
CrossAxisAlignment.stretch
的 Column
并且你只希望按钮不被拉伸时,就会发生上述情况:Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Align: without Align')),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Align(
child: RaisedButton(
onPressed: () {},
child: const Text('Button'),
),
),
],
),
);
}
总是当你的小部件不听从你尝试设置的约束时,首先尝试用 Align
包装它。
Container
最常用的小部件之一 - 并且有充分的理由:
Container as a layout tool
当您不指定 Container
的高度和宽度时,它将与其子容器的大小相匹配
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Container as a layout')),
body: Container(
color: Colors.yellowAccent,
child: Text("Hi"),
),
);
}
如果要拉伸 Container
以匹配其父级,请使用 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
您可以使用 color
属性来影响 Container
的背景、装饰和前景装饰。 (有了这两个属性,你可以完全改变 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
装饰通常用在容器小部件上以更改容器的外观。
image: DecorationImage
将图像作为背景:
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(
'https://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.png',
),
),
),
),
),
);
border: Border
指定容器的边框应该是什么样子。
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
使边框角变圆。
如果装饰的形状是 BoxShape.circle
,borderRadius
不起作用
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
盒子装饰可以是矩形/正方形或椭圆/圆形。
对于任何其他形状,您可以使用 ShapeDecoration
而不是 BoxDecoration
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<BoxShadow>
向容器添加阴影。
该参数是一个列表,因为您可以指定多个不同的阴影并将它们合并在一起。
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
。
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,
],
),
),
),
),
);
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],
),
),
),
),
);
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
的子项之上(而装饰则绘制在子项后面)。
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(
'https://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.png',
),
),
),
);
backgroundBlendMode
不仅仅影响它所在的容器。
backgroundBlendMode
更改容器中小部件树上任何内容的颜色。
以下代码有一个绘制图像的父容器和使用 backgroundBlendMode
的子容器。 尽管如此,您仍然会得到与以前相同的效果。
Scaffold(
appBar: AppBar(title: Text('backgroundBlendMode')),
body: Center(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://img.alicdn.com/imgextra/i3/O1CN01cc4qI41HXKUftY8IO_!!6000000000767-2-tps-200-80.png',
),
),
),
child: Container(
height: 200,
width: 200,
foregroundDecoration: BoxDecoration(
backgroundBlendMode: BlendMode.exclusion,
gradient: LinearGradient(
colors: const [
Colors.red,
Colors.blue,
],
),
),
),
),
),
);
Material
带有切角的边框
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
当你想要将内容居中时,即使没有足够的空间,此小部件也是不可替代的。
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
,内容会溢出,如下所示:填充剩余空间
除了有助于将内容居中之外,SliverFillRemaining
还将填充剩余视口的可用空间。 为此,这个小部件必须放置在 CustomScrollView
中,并且需要是最后一个条子
如果没有足够的空间,小部件将变为可滚动:
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 as ConstrainedBox
SizedBox
的工作方式与 ConstrainedBox
类似
SizedBox.expand(
child: Card(
child: Text('Hello World!'),
color: Colors.yellowAccent,
),
),
SizedBox as padding
当需要添加填充或边距时,您可以选择填充或容器小部件。 但它们可能比添加 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
很多时候你想根据布尔值隐藏/显示小部件
Widget build(BuildContext context) {
bool isVisible = ...
return Scaffold(
appBar: AppBar(
title: Text('isVisible = $isVisible'),
),
body: isVisible
? Icon(Icons.star, size: 150)
: const SizedBox(),
);
}
因为 SizedBox
有一个 const
构造函数,所以使用 const SizedBox()
非常简单。
一种更简单的解决方案是使用 Opacity
小部件并将不透明度值更改为 0.0 。 该解决方案的缺点是给定的小部件只是不可见,但仍然会占用空间。
SafeArea
在不同的平台上,有一些特殊区域,例如 Android
上的状态栏或 iPhone X
上的刘海,我们可能会避免在其下方绘制。
这个问题的解决方案是 SafeArea
小部件(不带/带 SafeArea
的示例)
Widget build(BuildContext context) {
return Material(
color: Colors.blue,
child: SafeArea(
child: SizedBox.expand(
child: Card(color: Colors.yellowAccent),
),
),
);
}
目前使用 Flutter 来布局还不是很熟练的朋友,希望看完本文后对你有一定的帮助。