0
点赞
收藏
分享

微信扫一扫

Flutter 学习 容器类Widget


文章目录

  • ​​1. 概述​​
  • ​​2. 填充 Padding​​
  • ​​3. 装饰容器 DecoratedBox​​
  • ​​3.1 BoxDecration​​
  • ​​4. 变换 Transform​​
  • ​​4.1 平移​​
  • ​​4.2 旋转​​
  • ​​4.3 缩放​​
  • ​​4.4 RotatedBox​​
  • ​​5. Container容器​​
  • ​​5.1 Padding 和Margin​​
  • ​​6. Clip​​
  • ​​6.1 CustomClipper​​
  • ​​7 FittedBox​​
  • ​​7.1 示例:单行缩放布局​​
  • ​​8. 脚手架 Scaffold​​
  • ​​8.1 Scaffold​​
  • ​​8.1.1 示例代码​​
  • ​​8.2 AppBar​​
  • ​​8.3 抽屉菜单 Drawer​​
  • ​​8.4 FloatingActionButton​​
  • ​​8.5 底部Tab导航栏​​

1. 概述

容器类和布局类都是接收子Widget展示,他们有很多相同点,而它们的不同点是:

  • 布局Widget一般接收一个 Widget 数组,它们直接或间接继承自​​MultiChildRenderObjectWidget​​,而容器类 Widget 一般只需要接收一个 子Wdiget,它们直接或间接继承​​SingleChildRenderObjectWidget​
  • 布局类 Widget 是按照一定的配列方式来对其子 Widget 进行排列,而 容器类Widget 一般只包装其 子Widget,对齐添加一些修饰、变化或限制

2. 填充 Padding

Padding定义:

class Padding extends SingleChildRenderObjectWidget {
const Padding({
...
required this.padding,
Widget? child,
})
...

padding 是 ​​EdgeInsetsGeometry​​​,我们一般使用 ​​EdgeInsets​​ ,它是子类,用于指定留白的大小,定义了一些便捷的方法:

  • ​formLTRB(...)​​ 分别指定四个方向的填充
  • ​all(...)​​ 所有方向均使用相同数组的填充
  • ​only(...)​​ 仅指定某几个方向的填充
  • ​symmetric(...)​​​ 用于设置对称方向的填充,例如 ​​vertical​​ 指上(top)下(bottom)

例子:

Padding(padding: EdgeInsets.all(20.0), // 所有方向留白20像素
child: Column(
children: const [
Padding(padding: EdgeInsets.only(right: 10),child: Text("右边留白"),),
Padding(padding: EdgeInsets.symmetric(vertical: 20),child: Text("上下留白"),),
Padding(padding: EdgeInsets.fromLTRB(5, 20, 20, 8),child: Text("四周留白")),
],
)),

Flutter 学习 容器类Widget_ico

3. 装饰容器 DecoratedBox

​DecoratedBox​​ 可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框或者渐变,定义如下:

class DecoratedBox extends SingleChildRenderObjectWidget {
const DecoratedBox({
required this.decoration,
this.position = DecorationPosition.background,
Widget? child,
})

  • decoration
    代表要绘制的装饰,类型是​​​Decoration​​​,它是一个抽象类,定义了一个接口​​createBoxPainter()​​,子类用其实现一个画笔,用于绘制装饰
  • ​position​​​ 此属性决定在哪里绘制 ​​Decoration​​,它接收 ​​DecorationPosition​​ 的枚举类型,该枚举类型的值为:
    ① ​​background​​:在子组件之后绘制,用于画背景
    ② ​​foreground​​: 在子组件之上绘制,用于画前景

decoration 一些默认的实现类有:

Flutter 学习 容器类Widget_缩放_02


来学习下 BoxDecoration

3.1 BoxDecration

我们通常会直接使用 ​​BoxDecration​​ 类,它是一个 Decration 的子类,用于实现常用的装饰元素的绘制。

const BoxDecoration({
this.color,
this.image,
this.border, // 边框
this.borderRadius, //圆角
this.boxShadow, // 阴影
this.gradient, // 渐变
this.backgroundBlendMode, // 背景混合模式
this.shape = BoxShape.rectangle, // 形状
})

下面来实现一个带阴影和渐变背景的按钮:

DecoratedBox(
decoration: BoxDecoration(
gradient:const LinearGradient(colors: [Colors.blue, Colors.purple]), // 匀速渐变
borderRadius: BorderRadius.circular(5.0), //圆角
boxShadow: const [
BoxShadow(
color: Colors.black54, //阴影颜色
offset: Offset(2.0, 2.0), // 阴影深度
blurRadius: 6.0 //阴影圆角
)
]
),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 80, vertical: 18.0),
child: Text("Rikka", style: TextStyle(color: Colors.white))
),
)

效果如下:

Flutter 学习 容器类Widget_导航栏_03

4. 变换 Transform

​Transform​​​ 可以在其子组件绘制时对其应用一些矩阵变换实现动画效果, ​​Matrix4​​ 是一个4D矩阵,通过它们可以实现不同的操作:

Container(
color: Colors.yellow,
child: Transform(
alignment: Alignment.topRight, // 相对坐标系原点的对齐方式
transform: Matrix4.skewY(0.3), // 沿着 Y 轴倾斜 0.3
child: Container(
padding: EdgeInsets.all(8.0),
color: Colors.deepOrange,
child: Text("This is Funker"),
),
),
)
]),

效果为:

Flutter 学习 容器类Widget_flutter_04

4.1 平移

使用 ​​Transform.translate​​​ 进行平移,它接收 ​​Offset​​ 参数,用于指定在 x、y轴对子组件的平移距离:

DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Transform.translate(
offset: Offset(20.0, 10.0), child: Text("Hello rikka")))

指定该Text的x轴向右平移20,y轴向下平移10,效果如下:

Flutter 学习 容器类Widget_ico_05

4.2 旋转

使用 ​​Transform.rotate​​​ 进行旋转,使用 ​​angle​​ 来指定旋转角度 :

import 'dart:math' as math;
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Transform.rotate(
angle: math.pi/3
, child: Text("Hello rikka")))

效果如下:

Flutter 学习 容器类Widget_导航栏_06

4.3 缩放

使用 ​​Transform.scale​​​ 进行缩放,使用 ​​angle​​ 来指定旋转角度 :

DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Transform.scale(scale: 2.0, child: Text("Hello rikka"))),

Flutter 学习 容器类Widget_缩放_07

4.4 RotatedBox

Transform 的变换阶段是在绘制阶段,这是在布局阶段之后,所以无论对子组件应用何种变化,其占用空间的大小和屏幕上的位置都是固定不变的。

用官方的例子来说,看下面代码:

Flutter 学习 容器类Widget_缩放_08


效果是这样的:

Flutter 学习 容器类Widget_缩放_09


这是因为第一个 Text 实际占据的部分就是红色区域,即使其子组件缩放,也不会改变组件的实际位置,而后面的Text是紧跟红色区域的,就产生了文字重叠。

为了解决这个问题,Flutter 封装了一些可以在布局阶段之后变换的Widget,比如 ​​RotatedBox​​,它是用于旋转的,代码示例如下:

Row(mainAxisAlignment: MainAxisAlignment.center, children: const [
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
// 旋转90度
child: RotatedBox(quarterTurns: 1, child: Text("Hello rikka"))),
Text(
"Hello",
style: TextStyle(color: Colors.blue),
)
])

Flutter 学习 容器类Widget_导航栏_10

5. Container容器

​Container​​​ 本身没有具体的 RenderObject,因为它继承的是 ​​StatelessWidget​​,它是用来组合 DecoratedBox、ConstrainedBox、Transform、Padding等组件的一个容器,它定义如下:

class Container extends StatelessWidget {
Container({
...
this.alignment,
this.padding,
this.color,
this.decoration,
this.foregroundDecoration,
double? width,
double? height,
BoxConstraints? constraints,
this.margin,
this.transform,
this.transformAlignment,
this.child,
this.clipBehavior = Clip.none,
})

来看看几个重要的

  • ​width​​​、​​height​​​ 可以指定容器的大小,同时​​constraints​​​ 也可以指定,如果同时存在,则优先使用​​witdh​​​和​​height​​, 实际上,constraints 也是由 width、height 来生成的
  • ​color​​​ 和​​decoration​​ 是互斥的,同时使用会报错, 而 decoration 是由 color 创建的

5.1 Padding 和Margin

这两个属性对 Android开发来已经是老朋友了, padding 用来留白、 margin 用来补白。 而 ​​Container​​​ 中使用 ​​Padding​​ 组件来实现的,例如下面代码:

Container(
margin: EdgeInsets.all(20.0),
color: Colors.blue,
child: Text("Hello Rikka"),
),
Container(
padding: EdgeInsets.all(20.0),
color: Colors.blue,
child: Text("Hello Rikka"),
)

和下面代码等价:

Padding(
padding: EdgeInsets.all(20.0),
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.blue),
child: Text("Hello Rikka"))),
DecoratedBox(
decoration: BoxDecoration(color: Colors.blue),
child: Padding(
padding: EdgeInsets.all(20.0), child: Text("Hello Rikka")))

Flutter 学习 容器类Widget_ico_11

6. Clip

来看下剪裁相关的 Widget:

  • ​ClipOval​​ 子组件为正方形时剪裁成内贴圆形,为矩形时,剪裁成内贴椭圆
  • ​ClipRRect​​ 将子组件剪裁为圆角矩形
  • ​ClipRect​​ 默认剪裁子组件布局空间之外的绘制内容
  • ​ClipPath​​ 按照自定义的路径剪裁

来看下例子:

override
Widget build(BuildContext context) {
Widget avatar = Image.asset("images/bobo.jpg", width: 60.0);
return Scaffold(
appBar: AppBar(
title: const Text("Basics Demo"),
),
body: Center(
child: Column(
children: [
avatar, //不剪裁
ClipOval(child: avatar), //剪成圆形
ClipRRect(
//剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Align(
alignment: Alignment.topLeft,
widthFactor: .5, //宽度设为原来宽度的一半,另一半则溢出,
child: avatar,
),
const Text("Hello Rikka",
style: TextStyle(color: Colors.blue))
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRect(
//将溢出部分裁剪
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5, //宽度设置为原来的一半
child: avatar,
),
),
const Text(
"Hello Rikka",
style: TextStyle(color: Colors.blue),
)
],
)
],
),
));
}

Flutter 学习 容器类Widget_ico_12


值得一提的是最后的两个 Row, 他们都设置了 ​​widthFactor​​ 为0.5,即将图片设置为原来的一半

  • 第一个​​Row​​ 图片溢出部分仍然会显示
  • 第二个​​Row​​ 剪裁掉了溢出的部分

6.1 CustomClipper

如果我们只想剪裁子组件的特定区域,例如图片中间的 60*60 像素范围,可以使用 ​​CustomClipper​​ 来自定义剪裁区域,使用如下:

class CenterClipper extends CustomClipper<Rect> {
@override
Rect getClip(Size size) =>const Rect.fromLTWH(15, 15, 30, 30);

@override
bool shouldReclip(covariant CustomClipper<Rect> oldClipper) => false;
}

  • ​getClip​​ 用于获取剪裁区域的接口,由于图片是 1000*1000像素,所以中间区域就是 (250, 250, 500, 500)
  • ​shouldReclip​​ 决定是否剪裁,如果剪裁区域是不中部发生变化,应该返回false,这样就不会触发重新剪裁,避免不必要的性能开销

接着我们来使用这个 Clip:

DecoratedBox(
decoration: const BoxDecoration(color: Colors.blue),
child: ClipRect(
clipper: CenterClipper(),
child: avatar,
),
)

Flutter 学习 容器类Widget_导航栏_13


这里就剪裁成功了,但是图片所占用控件大小依然是60*60,这是因为组件大小是在 layout 阶段确定的,而剪裁是在之后绘制进行的,这和 ​​Transform​​ 的原理类似。

7 FittedBox

我们开发中经常会遇到子元素超过父容器大小的情况。

比如将一张大图片显示在一个较小的区域,父组件会将自身最大的显示空间做为约束传递给子组件,子组件如果超出了这个约束,就要做一些缩小、裁剪。
例如 Text 组件如果其他父组件宽度固定,高度不限的话,则默认情况下 Text 会在文本达到父组件宽度时换行,那如果我们想让 Text 文本在超过父组件的宽度时不要换行而是字体缩小,或者在父组件宽高固定时,而Text文本比较小,此时想让文本放大以填充整个父组件空间该这么做呢?

这个问题的本质是 子组件如何适配父组件空间, Flutter提供了一 ​​FittedBox​​ 组件来解决这个问题,定义如下:

const FittedBox({
Key? key,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.clipBehavior = Clip.none,
Widget? child,
})

FittedBox的原理:

  1. ​FittedBox​​ 在布局子组件时,会忽略父组件传递的约束,可以允许子组件无限大
  2. ​FittedBox​​ 对子组件布局结束后获得子组件的真实大小
  3. ​FittedBox​​ 知道子组件的真实大小,也知道它父组件的约束,那么 FittedBox 就可以通过指定的适配方式(由 BoxFit 的枚举值),让子组件在 FittedBox 父组件的约束范围内按照指定的方式显示

下面是一个示例:

return Center(
child: Column(
children: [
wContainer(BoxFit.none),
Text("Rikka"),
wContainer(BoxFit.contain),
Text("The World"),
],
),
);
...
Widget wContainer(BoxFit boxFit) {
return Container(
width: 50,
height: 50,
color: Colors.red,
child: FittedBox(
fit: boxFit,
// 子容器超过父容器大小
child: Container(width: 80, height: 90, color: Colors.blue),
),
);
}

Flutter 学习 容器类Widget_flutter_14


​BoxFit.container​​ 就是按照子组件的比例进行缩放,尽可能多的占据父组件空间

7.1 示例:单行缩放布局

我们有三个数据,都需要在一行展示,换行是不能接受的。 如果数据过多,就会出现数据太长或屏幕太窄,无法显示在一行的情况,因此,我们希望如果无法一行显示时,要对组件进行适当的缩放,以保证一行能够显示的下。

override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Basics Demo"),
),
body: Center(
child: Column(
children: [
_wRow(" 90000000000000000000000000 "),
FittedBox(child: _wRow(" 90000000000000000000000000 ")),
_wRow(" 800 "),
FittedBox(child: _wRow(" 800 ")),
]
.map((e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: e,
))
.toList(),
),
),
);
}
...
Widget _wRow(String text) {
Widget result = Text(text);
result = Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [result, result, result],
);
return result;
}

Flutter 学习 容器类Widget_flutter_15


我们给 Row 在主轴的对齐方式是 ​​MainAxisAlignment.spaceEvenly​​ ,这会将水平方向的剩余显示空间均分成多份穿插在每一个 child 之间,也就是平等划分区域。

可以看到,当数字为 “90…” 时,三个数字的长度之和已经超出了屏幕的宽度,所以 Row 会有溢出,让给 Row 添加了 ​​FittedBox​​ 时,就可以按比例缩放至一行显示,实现了我们的效果。

但是当数字没有那么大时,如 “800”,直接使用 Row 是符合预期的,但是使用了 ​​FittedBox​​​ 却挤在了一起,不符合我们的预期。之所以会这样,是因为指定主轴对齐方式为 ​​sapceEvenly​​​: Row在布局时会拿到父组件的约束,如果约束的 maxWidth 不是无限大, Row 就会依据子组件的数量和它们的大小在主轴方向来根据 ​​spaceEvenly​​ 填充算法来分割水平的长度,最终 Row 的宽度为 maxWidth,但如果 maxWidth 为无限大,就无法进行分割了,所以此时 Row 就会将子组件宽度之和作为自己的宽度,导致出现这样的结果。

所以此时的解决方法,就是让 FittedBox 子元素接收到的约束的 maxWidth 为宽度屏幕即可,我们分装一个 SingleLineFittedBox 来替换 FittedBox 以达到预期效果,实现代码如下:

class SingleLineFittedBox extends StatelessWidget {
const SingleLineFittedBox({Key? key, this.child}) : super(key: key);

final Widget? child;

@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
return FittedBox(
child: ConstrainedBox(
constraints: constraints.copyWith(
// 让 maxWidth 使用屏幕宽度
maxWidth: constraints.maxWidth),
child: child,
));
});
}
}

然后使用它:

: [
_wRow(" 90000000000000000000000000 "),
SingleLineFittedBox(child: _wRow(" 90000000000000000000000000 ")),
_wRow(" 800 "),
SingleLineFittedBox(child: _wRow(" 800 ")),
]

Flutter 学习 容器类Widget_导航栏_16


这下下面修复了,但是上面却溢出了, 这是要因为:我们在 ​​SIngleLineFittedBox​​ 中将 Row 的maxWidth 设置为屏幕宽度后,效果和不加 SingleLineFittedBox 的效果是一样的,Row 收到父组件约束的 maxWidth 都是屏幕的宽度,这个是时候需要少加修改就可以实现:

class SingleLineFittedBox extends StatelessWidget {
const SingleLineFittedBox({Key? key, this.child}) : super(key: key);

final Widget? child;

@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
return FittedBox(
child: ConstrainedBox(
constraints: constraints.copyWith(
minWidth: constraints.maxWidth, maxWidth: double.infinity),
child: child,
));
});
}
}

我们将最小宽度(minWidth)约束指定为屏幕宽度,因为 Row 必须得遵守父组件的约束,所以 Row 的宽度至少等于屏幕宽度,所以就不会出现所在一起的情况,同时将 maxWidth 设置为无限大,就可以处理数字总长度超出屏幕宽度的情况。运行后如下:

Flutter 学习 容器类Widget_缩放_17

8. 脚手架 Scaffold

​Scaffold​​​ 是 Material 组件库中最常用的组件, 除此之外还有其他丰富多样的组件,可以自行查看 Flutter Gallery 中的 Material 组件部分示例。 它继承自 ​​StatefulWidget​​,组合了一些常用的属性。

8.1 Scaffold

Scaffold 一般作为一个路由页的骨架,我们可以使用它来简单地拼装出一个完整的页面。

8.1.1 示例代码

我们实现一个页面,它的包含有:

  • 一个导航栏 (AppBar)
  • 导航栏右边有一个分享按钮
  • 有一个抽屉菜单 (Drawer)
  • 有一个底部导航 (BottomNavigation)
  • 右下角有一个悬浮的动作按钮 (FloatingActionButton)

代码示例如下:

class _ScaffoldWidgetRouteState extends State<ScaffoldWidgetRoute> {
int _selectIndex = 1;

@override
Widget build(BuildContext context) {
return Scaffold(
// 导航栏
appBar: AppBar(
title: const Text("Scaffold Demo"),
actions: [
// 导航栏右侧菜单
IconButton(onPressed: () {}, icon: Icon(Icons.share))
],
),
// 抽屉菜单
drawer: Drawer(),
// 底部导航
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: "Unit"),
BottomNavigationBarItem(icon: Icon(Icons.school), label: "School")
],
currentIndex: _selectIndex,
fixedColor: Colors.blue,
onTap: (index) => {},
),
// 悬浮按钮
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => {},
),
);
}
}

效果如下:

Flutter 学习 容器类Widget_flutter_18


可以看到很轻松就实现了这样的页面,这就是 Scaffold 的魅力,接下来看下各个部件

8.2 AppBar

​AppBar​​ 是 Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等。

我们来看下 AppBar 的定义:

AppBar({
Key? key,
this.leading, // 导航栏最左侧的 Widget ,一般是抽屉,或者导航按钮
this.automaticallyImplyLeading = true, // 如果leading为null,是否自动实现默认leading的按钮
this.title, // 页面标题
this.actions, // 导航栏右侧菜单
this.flexibleSpace,
this.bottom, // 导航栏底部菜单,通常为 Tab按钮组
this.elevation, // 导航栏阴影
this.centerTitle, // 标题是否居中
...
})

如果给 ​​Scaffold​​​ 实现了 ​​drawer​​​ 也就是抽屉菜单, 默认情况下 Scaffold 会自动将 AppBar 的 ​​leading​​ 设置为菜单按钮,点击它就可以打开抽屉。 如果想自定义这个菜单图标,可以手动实现leading,如下代码:

: AppBar(
title: const Text("Scaffold Demo"),
leading: Builder(builder: (context) {
return IconButton(
onPressed: () => {Scaffold.of(context).openDrawer()},
icon: Icon(
Icons.dashboard,
color: Colors.white,
));
}),
...

使用 ​​Scaffold.of(context)​​​可以取得父级最近的 Scaffold 组件的 State 对象, 打开抽屉的方法已经被实现在了 ​​ScaffoldState​​ 中了。

效果为:

Flutter 学习 容器类Widget_flutter_19

8.3 抽屉菜单 Drawer

​Scaffold​​​ 有 ​​drawer​​​ 和 ​​endDrawer​​ 属性分别可以接受一个 Widget 来作为页面的左、右抽屉菜单。 如果开发者提供了抽屉菜单,那么当用户手指从屏幕左或右向里滑动时就可以打开抽屉菜单。

我们来实现一个 MyDrawer:

class MyDrawer extends StatelessWidget {
const MyDrawer({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
// 移除抽屉菜单顶部默认留白
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 40),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0),
child: ClipOval(
child: Image.asset(
"images/bobo.jpg", width: 80.0),
)),
const Text("Rika",
style: TextStyle(
fontWeight: FontWeight.bold))
])),
Expanded (
child: ListView(
children: const [
ListTile(
leading: const Icon(Icons.add),
title: Text("Add account"),
),
ListTile(
leading: const Icon(Icons.settings),
title: Text("Settings"),
),
],
),
)
],
))
);
}
}

并在 Scaffold 中使用 ​​drawer : MyDrawer()​​, 效果如下:

Flutter 学习 容器类Widget_导航栏_20


上面代码中的 ​​MediaQuery.removerPadding​​ 可以移除 Drawer 默认的留白(比如 Drawer 默认顶部会留和手机状态栏等高的空白)。 ListView 将在后面滚动组件中介绍。

8.4 FloatingActionButton

​FloatingActionButton​​​ 是一种特殊Button,通常悬浮在页面中的某一个位置,通过 ​​floatingActionButton​​ 来设置,不过国内的UI一般都不会看到有这样的按钮就是了

8.5 底部Tab导航栏

我们通过 ​​bottomNavigationBar​​​、​​BottomNavigationBarItem​​ 两个组件来来设置底部导航的按钮,比较简单。

除此之外,Material 组件还提供了一个 ​​BottomAppBar​​​,它可以和 ​​FloatingActionButton​​ , 效果比较有趣,代码如下:

: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(
icon: Icon(Icons.home),
onPressed: () {},
),
SizedBox(), //中间位置空出
IconButton(onPressed: () {}, icon: Icon(Icons.ac_unit))
],
// 均分底部导航栏横向空间
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
),
// 悬浮按钮
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => {},
),
);

其中 BottomAppBar 的shape 属性决定了洞的外形, ​​CircularNotchedRectangle​​ 是Flutter 默认帮我们实现的一个圆形外形,也可以自定义外形, Flutter Gallery 示例中实现了一个 “砖石” 形状的示例。

Flutter 学习 容器类Widget_缩放_21


举报

相关推荐

0 条评论