四、容器组件
定义
看了这么久,我div
呢?布局控件相对使用比较麻烦,容器组件感觉更贴近一些。
容器类Widget和布局类Widget都作用于其子Widget,不同的是:
- 布局类Widget一般都需要接收一个widget数组(children),他们直接或间接继承自(或包含)MultiChildRenderObjectWidget ;而容器类Widget一般只需要接收一个子Widget(child),他们直接或间接继承自(或包含)SingleChildRenderObjectWidget。
- 布局类Widget是按照一定的排列方式来对其子Widget进行排列;而容器类Widget一般只是包装其子Widget,对其添加一些修饰(补白或背景色等)、变换(旋转或剪裁等)、或限制(大小等)。
容器(Container)
它就是我们的div
吧...
它结合了绘制、定位和大小调整为一体。可以用它来设置背景颜色、形状、边框等。如果没有子组件,Container
会自动填充父组件;如果设置了子组件,Container
可以根据子组件的大小调整自身的大小。
Container({
this.alignment,
this.padding, //容器内补白,属于decoration的装饰范围
Color color, // 背景色
Decoration decoration, // 背景装饰
Decoration foregroundDecoration, //前景装饰
double width,//容器的宽度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制条件
this.margin,//容器外补白,不属于decoration的装饰范围
this.transform, //变换
this.child,
...
})
注意:
- 容器的大小可以通过
width
、height
属性来指定,也可以通过constraints
来指定;如果它们同时存在时,width
、height
优先。实际上Container内部会根据width
、height
来生成一个constraints
。 color
和decoration
是互斥的,如果同时设置它们则会报错!实际上,当指定color
时,Container
内会自动创建一个decoration
。
实现一个圆角带阴影的卡片示例:
Container(
margin: const EdgeInsets.only(top: 50.0, left: 120.0),
constraints: const BoxConstraints.tightFor(width: 200.0, height: 150.0), //卡片大小
// 前景装饰
// foregroundDecoration: BoxDecoration( // 前景装饰会把背景装饰和子组件都遮住
// color: Colors.blue,
// ),
//背景装饰
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), //圆角
gradient: const RadialGradient( // 背景径向渐变
colors: [Colors.red, Colors.orange],
center: Alignment.topLeft,
radius: .98, // 半径
),
boxShadow: const [ // 卡片阴影
BoxShadow(
color: Colors.black54,
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
)
],
),
transform: Matrix4.rotationZ(.2), //卡片倾斜变换
alignment: Alignment.center, //卡片内文字居中
child: const Text(
//卡片文字
"5.20", style: TextStyle(color: Colors.white, fontSize: 40.0),
),
)
示例
只有左上右上有圆角
Container(
width: 200,
height: 100,
decoration: BoxDecoration(
color: Color(0xff0075e0),
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
// color: Color(0xff0075e0),
)
渐变背景色
Container(
width: double.infinity,
height: 30,
// color: Colors.brown, // 如果有decoration属性,则不可直接设置Color属性,需移至decoration
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xfffbe8a3),Color(0xfffbe8a3), Color(0xfffef3d6),], // 渐变色
stops: [0.0, 0.7, 1.0], // 控制颜色的过渡点
),
borderRadius: BorderRadius.all(Radius.circular(1)), // 圆角
),
child: Row(
children: [
const SizedBox(width: 10,),
Image.asset(
'images/home/laba.png',
width: 18,
height: 18,
),
const SizedBox(width: 10,),
Expanded(child: CircularListMarquee()),
]
),
)
填充(Padding)
和css一样,内边距。Padding
组件用于给其子组件添加填充空间。它只接受一个子组件,并且可以指定上下左右的填充量。
Padding(
padding: EdgeInsets.all(20.0),
child: Text('Hello, Flutter!'),
)
EdgeInsets可以设置空间的内边距:
fromLTRB(double left, double top, double right, double bottom)
:分别指定四个方向的填充。注意与css的top right bottom left
顺序不同all(double value)
: 所有方向均使用相同数值的填充。only({left, top, right ,bottom })
:可以设置具体某个方向的填充(可以同时指定多个方向)。symmetric({ vertical, horizontal })
:用于设置对称方向的填充,vertical
指top
和bottom
,horizontal
指left
和right
。
样式(DecoratedBox)
可以在其子 widget 绘制前或绘制后绘制一个装饰(Decoration
)。这个装饰可以包括背景色、边框、边距、圆角、渐变、背景图像等。DecoratedBox
对于添加简单的装饰效果非常有用,而无需创建一个完整的 Container
widget,这使得布局更加轻量级。
const DecoratedBox({
Decoration decoration,
DecorationPosition position = DecorationPosition.background,
Widget? child
})
主要属性:
decoration
:这是DecoratedBox
的核心属性,类型为Decoration
。这个属性定义了如何绘制装饰,比如颜色、形状、阴影等。BoxDecoration
是Decoration
的一个常用子类,它提供了设置背景色、图片、边框、圆角等的能力。position
:这个属性决定装饰是绘制在子 widget 的背景之下还是前景之上,取值为DecorationPosition.background
或DecorationPosition.foreground
。child
:DecoratedBox
的子 widget,它位于装饰之上或之下,取决于position
的值。
BoxDecoration
BoxDecoration({
Color color, //颜色
DecorationImage image,//图片
BoxBorder border, //边框
BorderRadiusGeometry borderRadius, //圆角
List<BoxShadow> boxShadow, //阴影,可以指定多个
Gradient gradient, //渐变
BlendMode backgroundBlendMode, //背景混合模式
BoxShape shape = BoxShape.rectangle, //形状
})
属性 | 类型 | 描述 |
---|---|---|
color | Color | 背景颜色。注意,如果你同时使用了image ,color 就会作为图片的背景色。 |
image | DecorationImage | 背景图像。可以设置图片的填充方式、对齐方式等。 |
border | Border | 边框。可以单独设置每一边的样式,如颜色、宽度。 |
borderRadius | BorderRadius | 边框圆角。可以分别设置每个角的圆角大小。 |
boxShadow | List<BoxShadow> | 阴影列表。可以设置多个阴影,每个阴影可以有不同的颜色、偏移、模糊程度等。 |
gradient | Gradient | 渐变。可以设置线性渐变(LinearGradient )、径向渐变(RadialGradient )或扫描式渐变(SweepGradient )。 |
shape | BoxShape | 形状。可以是BoxShape.rectangle (矩形,默认值)或BoxShape.circle (圆形)。 |
backgroundBlendMode | BlendMode | 背景混合模式。用于与背景色混合,创建不同的视觉效果。 |
示例:
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
// 线性渐变
colors: [Colors.red, Colors.orange],
),
borderRadius: BorderRadius.circular(10.0), // 圆角
boxShadow: [
// 阴影
BoxShadow(
color: Colors.black54,
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
)
],
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
child: Text('Login', style: TextStyle(color: Colors.white)),
),
)
变换(Transform)
类似css中的transform。 Transform
控件可以对widget进行旋转、缩放、平移和倾斜等变换,而不改变widget本身的布局属性(如大小和位置)。Transform
直接作用于绘制阶段,因此不会影响布局,这意味着即使变换后的widget视觉上超出了原始布局空间,也不会影响其他widget的布局。
Container(
color: Colors.black,
child: Transform(
alignment: Alignment.topRight, //相对于坐标系原点的对齐方式
transform: Matrix4.skewY(0.3), //沿Y轴倾斜0.3弧度
child: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.deepOrange,
child: const Text('Apartment for rent!'),
),
),
)
主要属性:
transform
:这是Transform
widget 的核心属性,它接受一个Matrix4
对象,定义了如何变换widget。Matrix4
是一个4x4矩阵,用于描述3D变换。alignment
:定义变换的原点,默认是中心点Alignment.center
。变换(如旋转和缩放)是相对于这个点进行的。origin
:一个Offset
类型的点,也可以用来定义变换的原点。如果同时指定了origin
和alignment
,则origin
优先。child
:要变换的子widget。
基本示例:
DecoratedBox( // 旋转
decoration:BoxDecoration(color: Colors.blue),
child: Transform.rotate(
//旋转90度
angle:math.pi/2 ,
child: Text("Hello world"),
),
),
SizedBox(height: 50,),
DecoratedBox( // 缩放
decoration:BoxDecoration(color: Colors.green),
child: Transform.scale(
scale: 1.5, //放大到1.5倍
child: Text("Hello world")
)
),
SizedBox(height: 50,),
DecoratedBox( // 平移
decoration:BoxDecoration(color: Colors.red),
//默认原点为左上角,左移20像素,向上平移5像素
child: Transform.translate(
offset: Offset(-20.0, -5.0),
child: Text("Hello world"),
),
),
SizedBox(height: 80,),
Container( // 斜切
color: Colors.black,
child: Transform(
alignment: Alignment.topRight, //相对于坐标系原点的对齐方式
transform: Matrix4.skewY(0.3), //沿Y轴倾斜0.3弧度
child: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.orange,
child: const Text('Apartment for rent!'),
),
),
)
RotatedBox
RotatedBox
和Transform.rotate
功能相似,它们都可以对子组件进行旋转变换,但是有一点不同:RotatedBox
的变换是在layout阶段,会影响在子组件的位置和大小
DecoratedBox(
decoration:BoxDecoration(color: Colors.red),
//默认原点为左上角,左移20像素,向上平移5像素
child: Transform.translate(
offset: Offset(-20.0, -5.0),
child: Text("Hello world"),
),
),
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
//将Transform.rotate换成RotatedBox
child: RotatedBox(
quarterTurns: 1, //旋转90度(1/4圈)
child: Text("Hello world"),
),
),
由于RotatedBox
是作用于layout阶段,所以子组件会旋转90度(而不只是绘制的内容),decoration
会作用到子组件所占用的实际空间上,所以最终就是上图的效果
思考
使用Transform
对其子组件先进行平移然后再旋转和先旋转再平移,两者最终的效果一样吗?
在使用 Transform
对其子组件进行变换时,先进行平移然后再旋转与先旋转再平移的效果一般是不一样的。这是因为变换(Transformations)是按照指定的顺序应用的,且变换的效果是相对于当前坐标系来说的。在变换的过程中,每一步变换都可能改变后续变换的基准坐标系。
先平移再旋转:
当你先对一个组件进行平移操作,你是在当前的坐标系中移动了组件的位置。接着当你对组件进行旋转时,旋转是相对于旋转中心(默认是组件的中心)在已平移的新位置上进行的。
先旋转再平移:
相反,如果你先旋转组件,组件将围绕旋转中心(或指定的旋转点)旋转。此时如果再对组件进行平移,平移将在已旋转的坐标系中进行,这意味着平移的方向可能与原始坐标系中的方向不同。
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 先平移再旋转
Transform.translate(
offset: Offset(50, 0), // 向右平移50个单位
child: Transform.rotate(
angle: math.pi / 2, // 旋转90度
child: Container(
color: Colors.red,
width: 100,
height: 100,
child: Text('先平移再旋转'),
),
),
),
SizedBox(height: 50), // 添加一些间隔
// 先旋转再平移
Transform.rotate(
angle: math.pi / 2, // 旋转90度
child: Transform.translate(
offset: Offset(50, 0), // 向右平移50个单位
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: Text('先旋转再平移'),
),
),
),
],
),
同时可以看到,变换规则是父组件->子组件
裁剪(Clip) - overflow
剪裁(Clipping)是一种常用的UI效果,用于对widget进行形状裁剪或尺寸限制,使其显示区域被限制在特定的形状或路径内。Flutter提供了多种剪裁widget来实现不同的剪裁效果:
默认裁剪
剪裁Widget | 默认行为 |
---|---|
ClipOval | 子组件为正方形时剪裁成内贴圆形;为矩形时,剪裁成内贴椭圆 |
ClipRRect | 将子组件剪裁为圆角矩形 |
ClipRect | 默认剪裁掉子组件布局空间之外的绘制内容(溢出部分剪裁) |
ClipPath | 按照自定义的路径剪裁 |
Widget build(BuildContext context) {
final Widget avatar = Image.asset("images/pyy.jpeg", width: 60.0, height: 60, fit: BoxFit.fill,); // 设置图像的裁剪效果可以调整fit属性
return Scaffold(
appBar: AppBar(
title: const Text(
'容器',
),
centerTitle: true, // 文本居中
backgroundColor: Colors.cyanAccent.shade700,
),
body: Center(
child: Column(
children: <Widget>[
avatar, //不剪裁
ClipOval(child: avatar), //剪裁为圆形
ClipRRect(
//剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
widthFactor: .5, // 宽度设为原来宽度一半,另一半会溢出
child: avatar,
),
Text(
"你好世界",
style: TextStyle(color: Colors.green),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRect(
//将溢出部分剪裁
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5, //宽度设为原来宽度一半
child: avatar,
),
),
Text("你好世界", style: TextStyle(color: Colors.green))
],
),
],
),
));
}
widthFactor
是Align
和其他一些布局widget的一个属性,用于确定子widget的宽度相对于其父widget的大小 最后的两个Row
通过Align
设置widthFactor
为0.5后,图片的实际宽度等于60×0.5,即原宽度一半,但此时图片溢出部分依然会显示。 所以第一个“你好世界”会和图片的另一部分重合,为了剪裁掉溢出部分,我们在第二个Row
中通过ClipRect
将溢出部分剪裁掉了。
自定义裁剪(CustomClipper)
如果我们想剪裁子组件的特定区域,比如,在上面示例的图片中,如果我们只想截取图片中部40×30像素的范围应该怎么做?这时我们可以使用CustomClipper
来自定义剪裁区域,实现代码如下:
class MyClipper extends CustomClipper<Rect> {
Rect getClip(Size size) => Rect.fromLTWH(100.0, 0, 300.0, 300.0); // left,top,width,height
bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
}
getClip()
是用于获取剪裁区域的接口,由于图片大小是499x285,我们返回剪裁区域为Rect.fromLTWH(100.0, 0, 300.0, 300.0)
,即图片中部40×30像素的范围。shouldReclip()
接口决定是否重新剪裁。如果在应用中,剪裁区域始终不会发生变化时应该返回false
,这样就不会触发重新剪裁,避免不必要的性能开销。如果剪裁区域会发生变化(比如在对剪裁区域执行一个动画),那么变化后应该返回true
来重新执行剪裁。
final Widget avatar = Image.asset("images/pyy.jpeg", width: 375, height: 285, fit: BoxFit.cover,);
DecoratedBox(
decoration: BoxDecoration(
color: Colors.red
),
child: ClipRect(
clipper: MyClipper(), //使用自定义的clipper
child: avatar
),
),
avatar, //不剪裁
可以看到我们的剪裁成功了,但是图片所占用的空间大小仍然是375×285(红色区域),这是因为组件大小是是在layout阶段确定的,而剪裁是在之后的绘制阶段进行的,所以不会影响组件的大小,这和Transform
原理是相似的。
ClipPath 可以按照自定义的路径实现剪裁,它需要自定义一个CustomClipper<Path>
类型的 Clipper,定义方式和 MyClipper 类似,只不过 getClip
需要返回一个 Path:
// 改写刚才的例子
class MyClipperPath extends CustomClipper<Path> {
Path getClip(Size size) {
// 定义一个新的路径
var path = Path();
// 从左上角(100.0, 0)开始
path.moveTo(100.0, 0);
// 定义一个矩形路径,与原来的Rect.fromLTWH(100.0, 0, 300.0, 300.0)等价
path.lineTo(100.0, 300.0); // 向下画线
path.lineTo(400.0, 300.0); // 向右画线
path.lineTo(400.0, 0); // 向上画线
path.close(); // 闭合路径
return path;
}
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
空间适配(FittedBox)
子组件大小超出了父组件大小时,如果不经过处理的话 Flutter 中就会显示一个溢出警告并在控制台打印错误日志:
当看到黄色和黑色条纹警告,这通常表示某个widget超出了其父widget的布局约束。在这个例子中,Row
widget 包含一个文本(Text('xyz' * 50)
),这个文本的内容超过了屏幕的宽度。
Row
widget 和其他Flex类widget(如 Column
和 Flex
)默认情况下不会对其子widget进行剪裁。这意味着如果子widget超出了 Row
的边界,它会在界面上显示为溢出内容的视觉提示,即那些黄色和黑色的条纹。
在这个特定的例子中,虽然 Container
设置了无限宽度的约束(BoxConstraints(minWidth: double.infinity)
),但是 Row
仍然尝试让所有的子widget在水平方向上排列,而不考虑它们的总宽度是否超过屏幕宽度。因此,当文本内容超过屏幕宽度时,Row
的子widget(在这里是文本)就会溢出,并在界面上显示溢出的警告。
抛出需求: 如果我们想让 Text 文本在超过父组件的宽度时不要换行而是字体缩小呢?还有一种情况,比如父组件的宽高固定,而 Text 文本较少,这时候我们想让文本放大以填充整个父组件空间该怎么做呢?实际上,这两个问题的本质就是:子组件如何适配父组件空间。
定义
FittedBox
根据自身的大小和子 widget 的大小,对子 widget 进行缩放和定位,以确保子 widget 能够根据指定的适配方式完全填充 FittedBox
的空间。这对于解决不同屏幕尺寸和分辨率导致的布局问题特别有用,是实现空间适配的一种常见方法。
const FittedBox({
Key? key,
this.fit = BoxFit.contain, // 适配方式
this.alignment = Alignment.center, //对齐方式
this.clipBehavior = Clip.none, //是否剪裁
Widget? child,
})
BoxFit 枚举值 | 描述 |
---|---|
fill | 子widget会被拉伸来完全填充FittedBox的空间,可能会导致子widget的宽高比失衡。 |
contain | 保持子widget的宽高比,使得子widget尽可能大地填充,同时确保其完整显示在FittedBox内部。 |
cover | 保持子widget的宽高比,尽可能小地填充,确保FittedBox的每个部分都被子widget覆盖。 |
fitWidth | 保持子widget的宽高比,缩放子widget以填充FittedBox的宽度。 |
fitHeight | 保持子widget的宽高比,缩放子widget以填充FittedBox的高度。 |
适配原理
- FittedBox 在布局子组件时会忽略其父组件传递的约束,可以允许子组件无限大,即FittedBox 传递给子组件的约束为(0<=width<=double.infinity, 0<= height <=double.infinity)。
- FittedBox 对子组件布局结束后就可以获得子组件真实的大小。
- FittedBox 知道子组件的真实大小也知道他父组件的约束,那么FittedBox 就可以通过指定的适配方式(BoxFit 枚举中指定),让起子组件在 FittedBox 父组件的约束范围内按照指定的方式显示。
示例
Widget wContainer(BoxFit boxFit) {
return Container(
width: 50,
height: 50,
color: Colors.red,
child: FittedBox(
fit: boxFit,
// 子容器超过父容器大小
child: Container(width: 60, height: 70, color: Colors.blue),
),
);
}
body: Center(
child: Column(
children: [
wContainer(BoxFit.none),
Text('上方区域有越界'),
wContainer(BoxFit.contain),
Text('正常展示'),
],
),
因为父Container要比子Container 小,所以当没有指定任何适配方式时,子组件会按照其真实大小进行绘制:
- 第一个蓝色区域会超出父组件的空间,因而看不到红色区域
- 第二个我们指定了适配方式为 BoxFit.contain,含义是按照子组件的比例缩放,尽可能多的占据父组件空间,因为子组件的长宽并不相同,所以按照比例缩放适配父组件后,父组件能显示一部分
把子组件的蓝色背景删除看下效果:
在未指定适配方式时,虽然 FittedBox 子组件的大小超过了 FittedBox 父 Container 的空间,但FittedBox 自身还是要遵守其父组件传递的约束,所以最终 FittedBox 的本身的大小是 50×50,这也是为什么蓝色会和下面文本重叠的原因,因为在布局空间内,父Container只占50×50的大小,接下来文本会紧挨着Container进行布局,而此时Container 中有子组件的大小超过了自己,所以最终的效果就是绘制范围超出了Container,但布局位置是正常的,所以就重叠了。
如果我们不想让蓝色超出父组件布局范围,那么可以使用 ClipRect 包裹wContainer中的Container对超出的部分剪裁掉即可
页面框架(Scaffold)- 重要
Scaffold
是 Flutter 中的一个非常重要的布局组件,它提供了一个框架,用于实现基本的材料设计布局结构。Scaffold
提供了默认的导航栏、标题、包含主屏幕widget树的body属性、一个浮动操作按钮、抽屉菜单(通常用于创建导航抽屉)等等。
属性 | 类型 | 描述 |
---|---|---|
appBar | PreferredSizeWidget | 显示在界面顶部的AppBar。 |
body | Widget | 当前界面所显示的主要内容Widget。 |
floatingActionButton | Widget | 浮动在body上的按钮,通常用于执行应用程序中的主要动作。 |
drawer | Widget | 抽屉菜单,通常是一个Drawer widget,从屏幕边缘滑入。 |
bottomNavigationBar | Widget | 底部导航条,可以有多个BottomNavigationBarItem组成。 |
backgroundColor | Color | Scaffold的背景颜色。 |
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Scaffold 示例'),
actions: <Widget>[
//导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
),
body: Center(
child: Text('Hello, Scaffold!'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 浮动动作按钮的点击事件
print('FloatingActionButton tapped');
},
child: Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: '学习',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: '学习',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: '学习',
),
],
type: BottomNavigationBarType.fixed, // tabbar超过3个,不加该属性会展示异常
currentIndex: 0,
selectedItemColor: Theme.of(context).colorScheme.primary,
// 切换页面
onTap: (index) {
print(index);
},
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('抽屉头部'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('项 1'),
onTap: () {
// 更新应用状态...
Navigator.pop(context); // 关闭抽屉菜单
},
),
ListTile(
title: Text('项 2'),
onTap: () {
// 更新应用状态...
Navigator.pop(context); // 关闭抽屉菜单
},
),
// ... 其他列表项
],
),
),
),
);
}
}
AppBar
AppBar
是一个Material风格的导航栏,通过它可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等。下面我们看看AppBar的定义:
AppBar({
Key? key,
this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
this.title,// 页面标题
this.actions, // 导航栏右侧菜单
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, //标题是否居中
this.backgroundColor,
... //其他属性见源码注释
})