通过上一篇《flutter自学笔记2-了解Flutter UI 页面和跳转》, 从整体上初步了解了一点页面构造和跳转。
笔记3:本篇我记录常用的一些Widget,以此为后面开发复杂界面做准备,也方便快速查找和回忆,后续也会不断完善
注:
一、基础组件
1.1 Text
Text 组件支持很多样式设置,比如颜色(color)、字体粗细(fontWeight)、字体样式(fontStyle)、文字装饰(decoration,比如下划线)、文字对齐方式(textAlign)等等。这些都可以通过 TextStyle 类来设置。
maxLines、overflow:指定文本显示的最大行数
例如,如果你想让文本显示为红色,你可以这样做:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Text Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Text Demo'),
),
body: Center(
child: Text(
'你好,Flutter!',
style: TextStyle(
fontSize: 24,
color: Colors.red, // 设置文本颜色为红色
height: 2.0, // 行高通常是字体大小的倍数,这里设置为2.0倍
fontFamily: "Courier", // 设置字体为Courier
background: Paint()..color = Colors.yellow, // 设置文本背景颜色为黄色
decoration: TextDecoration.underline, // 设置文本下划线
decorationStyle: TextDecorationStyle.dashed, // 设置下划线为虚线
),
),
),
),
);
}
}
1.2 TextSpan
TextSpan,它代表文本的一个“片段”
使用 TextSpan 和 Text.rich() 方法构建富文本的示例:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter RichText Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter RichText Demo'),
),
body: Center(
child: RichText(
text: TextSpan(
text: '这是一个',
style: TextStyle(fontSize: 20),
children: [
TextSpan(
text: '富文本',
style: TextStyle(fontSize: 24, color: Colors.blue),
),
TextSpan(
text: '示例。',
style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),
),
],
),
),
),
),
);
}
}
1.3 字体
系统字体 :设置 fontFamily
Text(
'Hello, Flutter!',
style: TextStyle(
fontFamily: 'Roboto', // 使用系统字体Roboto
fontSize: 18.0,
),
)
1.4 自定义字体
将字体文件(如 .ttf 或 .otf 文件)添加到 Flutter 项目的 assets 目录中,并在 pubspec.yaml 文件中声明这些字体文件。然后,他们就可以在 fontFamily 属性中引用这些自定义字体的名称了:
- 将
MyCustomFont.ttf文件添加到 Flutter 项目的assets/fonts/目录中(如果目录不存在,可以手动创建)。 - 在
pubspec.yaml文件中声明这个字体文件:
flutter:
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
3.在 TextStyle 中使用 fontFamily 属性来引用这个自定义字体:
Text(
'Hello, Custom Font!',
style: TextStyle(
fontFamily: 'MyCustomFont', // 使用自定义字体MyCustomFont
fontSize: 18.0,
),
)
1.5 按钮
- ElevatedButton(凸起按钮): 这是一个具有明显凸起效果的按钮,通常用于主要操作。
ElevatedButton(
onPressed: () {
// 处理点击事件
},
child: Text('Elevated Button'),
)
- TextButton(文本按钮): 这是一个简单的文本按钮,通常用于不太重要的操作或需要节省空间的地方。
TextButton(
onPressed: () {
// 处理点击事件
},
child: Text('Text Button'),
)
- OutlinedButton(轮廓按钮): 这是一个带有轮廓线的按钮,通常用于需要强调边界但不希望按钮过于显眼的情况。
OutlinedButton(
onPressed: () {
// 处理点击事件
},
child: Text('Outlined Button'),
)
- IconButton(图标按钮): 这是一个仅包含图标的按钮,通常用于工具栏或需要节省空间的地方。
IconButton(
icon: Icon(Icons.add),
onPressed: () {
// 处理点击事件
},
)
- FloatingActionButton(浮动操作按钮): 这是一个通常位于屏幕底部或角落的圆形按钮,用于执行主要操作,如添加、编辑等。
FloatingActionButton(
onPressed: () {
// 处理点击事件
},
tooltip: 'Floating Action Button',
child: Icon(Icons.add),
)
- ButtonStyleButton(自定义样式按钮): 虽然这不是一个具体的按钮类,但你可以通过
ButtonStyle来高度自定义按钮的外观和行为。
Button(
onPressed: () {
// 处理点击事件
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
overlayColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return Colors.blue.withAlpha(128);
}
return null;
},
),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
),
),
child: Text('Custom Style Button'),
)
1.6 图标
Icon 组件
Icon(
Icons.add, // 这是一个Material Design图标集中的“添加”图标
size: 24, // 设置图标的大小
color: Colors.blue, // 设置图标的颜色
)
IconButton 显示图标的按钮
IconButton(
icon: Icon(Icons.add), // 设置按钮的图标
onPressed: () {
// 处理按钮点击事件
},
tooltip: 'Add item', // 设置按钮的提示文本,当用户长按按钮时显示
color: Colors.blue, // 设置按钮(和图标)的颜色
)
自定义图标
Image.asset(
'assets/icons/my_custom_icon.png', // 图标文件的路径
width: 24, // 设置图标的宽度
height: 24, // 设置图标的高度
color: Colors.blue, // 设置图标的颜色(如果图标是透明的,这个属性将有效)
)
1.7 单选开关和复选框
单选开关(Switch)
-
基本属性
value:表示开关的当前状态,true表示打开,false表示关闭。onChanged:当开关状态改变时触发的回调函数,用于更新UI或执行其他逻辑。activeColor:开关打开时的颜色。inactiveThumbColor:开关关闭时滑块(拇指)的颜色。inactiveTrackColor:开关关闭时轨道的颜色。activeThumbImage和inactiveThumbImage:分别用于设置开关打开和关闭时滑块上的图像。
- 使用示例
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Switch Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isSwitched = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Switch Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Switch is: $_isSwitched',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
Switch(
value: _isSwitched,
onChanged: (value) {
setState(() {
_isSwitched = value;
});
},
activeColor: Colors.green,
inactiveThumbColor: Colors.red,
inactiveTrackColor: Colors.grey,
),
],
),
),
);
}
}
-
iOS风格的单选开关
Flutter还提供了
CupertinoSwitch组件,用于创建iOS风格的单选开关。CupertinoSwitch( value: _isSwitched, onChanged: (value) { // 处理开关状态改变 }, )
复选框(Checkbox)
-
基本属性
value:表示复选框的当前状态,true表示选中,false表示未选中。onChanged:当复选框状态改变时触发的回调函数,用于更新UI或执行其他逻辑。tristate:一个可选属性,如果设置为true,则复选框可以处于三种状态:选中(true)、未选中(false)和不确定(null)。这在全选/全不选操作中非常有用。side:用于设置复选框的边线颜色、宽度等属性。shape:用于设置复选框的形状,如圆角矩形、圆形等。
-
使用示例
Checkbox( value: _isChecked, onChanged: (value) { setState(() { _isChecked = value!; }); }, )
-
CupertinoCheckbox
Flutter中的
Checkbox组件属于Material风格,对于需要macOS风格的复选框,可以使用CupertinoCheckbox组件。
CupertinoCheckbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value!;
});
},
tristate: false, // 是否启用三态
side: BorderSide(color: Colors.black12), // 边线属性
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), // 形状属性
)
1.8 输入框(TextField)
TextField是Flutter中的一个基础组件,它允许用户在应用中输入文本。TextField提供了多种属性来定制其外观和行为,包括但不限于:
- controller:编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数情况下,开发者需要显式提供一个controller来与文本框进行交互。
- focusNode:用于控制TextField是否占有当前键盘的输入焦点。它是我们和键盘交互的一个句柄。
- decoration:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
- keyboardType:用于设置该输入框默认的键盘输入类型,如文本输入、数字输入、电话号码输入等。
- textInputAction:键盘动作按钮图标(即回车键位图标),它是一个枚举值,有多个可选值,如完成、下一步、搜索等。
- style:正在编辑的文本样式。
- textAlign:输入框内编辑文本在水平方向的对齐方式。
- autofocus:是否自动获取焦点。
- obscureText:是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
- maxLines:输入框的最大行数,默认为1;如果为null,则无行数限制。
- maxLength和maxLengthEnforcement:maxLength代表输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数。maxLengthEnforcement决定当输入文本长度超过maxLength时如何处理,如截断、超出等。
此外,TextField还提供了多种回调函数,如onChange(内容改变时的回调)、onEditingComplete和onSubmitted(输入完成时触发)等,用于处理用户输入事件。
1.9 表单(Form)
Flutter中的Form组件可以对输入框进行分组,并进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。Form继承自StatefulWidget对象,它对应的状态类为FormState。
Form组件的主要属性包括:
- autovalidate:是否自动校验输入内容。当为true时,每一个子FormField内容发生变化时都会自动校验合法性,并直接显示错误信息。否则,需要通过调用FormState.validate()来手动校验。
- onWillPop:决定Form所在的路由是否可以直接返回(如点击返回按钮)。该回调返回一个Future对象,如果Future的最终结果是false,则当前路由不会返回;如果为true,则会返回到上一个路由。此属性通常用于拦截返回按钮。
- onChanged:Form的任意一个子FormField内容发生变化时会触发此回调。
1.10 进度指示器
1、线性进度指示器(LinearProgressIndicator)
线性进度指示器是水平显示的进度条,通常用于表示任务的确定进度或不确定进度(即任务正在进行但具体进度未知)。
LinearProgressIndicator(
value: 0.5, // 设置进度值,范围为0.0到1.0
backgroundColor: Colors.grey[200], // 设置背景颜色
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), // 设置进度条颜色
)
循环模式
当value为null时,进度指示器将进入不确定模式,显示一个循环动画。
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
)
2、圆形进度指示器(CircularProgressIndicator)
圆形进度指示器是类似于加载旋转轮的圆形进度条,适用于任何方向的空间。
CircularProgressIndicator(
value: 0.5, // 设置进度值,范围为0.0到1.0
backgroundColor: Colors.grey[200], // 设置背景颜色
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), // 设置进度条颜色
strokeWidth: 4.0, // 设置圆形进度条的粗细
)
同样地,当value为null时,圆形进度指示器也将进入不确定模式。
3、Step Progress Indicator( 步骤进度指示器)
Step Progress Indicator是一个轻量级且高度定制化的Flutter包,它以一系列可选和未选中的步骤构成条形或圆形进度指示器。
在pubspec.yaml文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
step_progress_indicator: ^1.0.2
然后运行flutter pub get来安装依赖。
使用
Step Progress Indicator提供了高度自定义的API,允许开发者调整颜色、大小、形状以及动画效果等。
import 'package:step_progress_indicator/step_progress_indicator.dart';
// ...
StepProgressIndicator(
totalSteps: 5, // 设置总步骤数
currentStep: 3, // 设置当前步骤
size: 24.0, // 设置步骤图标的大小
completedColor: Colors.blue, // 设置已完成步骤的颜色
uncompletedColor: Colors.grey, // 设置未完成步骤的颜色
// 其他自定义属性...
)
4、液态进度指示器(Liquid Progress Indicator)
液态进度指示器提供了三种类型的进度指示器:液态圆形进度指示器、液态线性进度指示器和液态自定义进度指示器。
在pubspec.yaml文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
liquid_progress_indicator: ^0.4.0
然后运行flutter pub get来安装依赖。
使用
import 'package:liquid_progress_indicator/liquid_progress_indicator.dart';
// ...
LiquidCircularProgressIndicator(
value: 0.25, // 设置进度值,范围为0.0到1.0
valueColor: AlwaysStoppedAnimation<Color>(Colors.pink), // 设置进度颜色
backgroundColor: Colors.white, // 设置背景颜色
borderColor: Colors.red, // 设置边框颜色
borderWidth: 5.0, // 设置边框宽度
direction: Axis.vertical, // 设置进度方向
center: Text("加载中..."), // 设置中心文本
)
1.11 弹窗
AlertDialog: AlertDialog 是一种最常见的对话框,用于显示信息或接收用户确认。
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('标题'),
content: Text('这是对话框的内容'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('取消'),
),
TextButton(
onPressed: () {
// 执行确认操作
Navigator.of(context).pop();
},
child: Text('确定'),
),
],
);
},
);
SimpleDialog: SimpleDialog 提供一个带有选项列表的对话框,用户可以从中选择一个选项。
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text('选择选项'),
children: <Widget>[
SimpleDialogOption(
child: Text('选项1'),
onPressed: () {
Navigator.of(context).pop('选项1');
},
),
SimpleDialogOption(
child: Text('选项2'),
onPressed: () {
Navigator.of(context).pop('选项2');
},
),
],
);
},
).then((value) {
if (value != null) {
// 处理用户选择的选项
print('用户选择了: $value');
}
});
ConfirmationDialog(自定义): Flutter 并没有直接提供一个名为 ConfirmationDialog 的组件,但你可以通过 AlertDialog 实现一个确认对话框。
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('确认'),
content: Text('您确定要继续吗?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text('确定'),
),
],
);
},
).then((value) {
if (value == true) {
// 用户点击了确定
print('用户确认了操作');
} else {
// 用户点击了取消
print('用户取消了操作');
}
});
BottomSheet: BottomSheet 是一种从屏幕底部滑出的对话框,常用于展示更多选项或详细信息。
showBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('这是一个 BottomSheet'),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('关闭'),
),
],
),
);
},
);
FullScreenDialog(自定义): 虽然 Flutter 没有直接提供全屏对话框的组件,但你可以通过调整 Dialog 的样式和布局来实现一个全屏对话框。
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0.0)
),
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(0.0)
),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width,
maxHeight: MediaQuery.of(context).size.height
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('这是一个全屏对话框'),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('关闭'),
),
],
),
),
);
},
);
1.12 应用:登录页
创建一个包含用户名和密码输入框的表单,并在用户点击提交按钮时进行验证:
代码示例:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Form Example',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Form Example'),
),
body: MyForm(),
),
);
}
}
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
String _username = '';
String _password = '';
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: Colors.grey),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '用户名不能为空';
}
return null;
},
onSaved: (value) {
_username = value!;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: TextFormField(
decoration: InputDecoration(
labelText: '密码',
hintText: '请输入密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: Colors.grey),
),
suffixIcon: IconButton(
icon: Icon(Icons.visibility),
onPressed: () {
// 实现密码可见性切换的逻辑
},
),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return '密码不能为空';
}
return null;
},
onSaved: (value) {
_password = value!;
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('用户名: $_username, 密码: $_password'),
),
);
}
},
child: Text('提交'),
),
),
],
),
);
}
}
二、布局类组件
布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排列(layout)方式不同:
| Widget | 说明 | 用途 |
|---|---|---|
| LeafRenderObjectWidget | 非容器类组件基类 | Widget树的叶子节点,用于没有子节点的widget,通常基础组件都属于这一类,如Image。 |
| SingleChildRenderObjectWidget | 单子组件基类 | 包含一个子Widget,如:ConstrainedBox、DecoratedBox等 |
| MultiChildRenderObjectWidget | 多子组件基类 | 包含多个子Widget,一般都有一个children参数,接受一个Widget数组。如Row、Column、Stack等 |
2.1 RenderBox
RenderBox 是一个基础渲染类,通常不直接使用,而是作为其他渲染类的基类。它代表了一个具有位置和大小的矩形框。以下是一个简单的自定义 RenderBox 子类示例,但请注意,在实际应用中,您通常会使用 SingleChildRenderObjectWidget 来包装它:
// 通常不直接这样使用,而是作为自定义渲染类的基类
class MyRenderBox extends RenderBox {
// 构造函数和其他必要的方法需要实现,这里仅作为示例
// ...
}
由于 RenderBox 是底层渲染类,通常不需要在Flutter的Dart代码中直接使用它,而是使用更高层次的Widget。
2.2 Sliver (RenderSliver)
Sliver 是一个用于滚动视图中表示一个可滚动片段的抽象类。RenderSliver 是其渲染类。Sliver 通常与 CustomScrollView 和 SliverList、SliverGrid 等一起使用。以下是一个使用 SliverList 的简单示例:
CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(
[
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
// ...更多项
],
),
),
],
)
2.3 BoxConstraints
BoxConstraints 用于描述一个盒子(Widget)可以接受的布局约束。以下是一个在自定义布局或Widget中使用 BoxConstraints 的简单示例:
class MyCustomWidget extends SingleChildRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return MyCustomRenderObject();
}
}
class MyCustomRenderObject extends RenderBox {
@override
void performLayout() {
// 获取传递给此RenderBox的BoxConstraints
final BoxConstraints constraints = this.constraints;
// 假设子Widget将填满可用空间(假设有子Widget)
if (child != null) {
child!.layout(constraints.loosen()); // 可以稍微放宽约束,但通常直接使用constraints
}
// 设置此RenderBox的大小(这里简单地设置为与约束相同)
size = constraints.constrain(Size(
double.infinity, // 通常会根据子Widget或特定逻辑设置宽度和高度
double.infinity,
));
}
}
请注意,上面的 MyCustomRenderObject 示例是为了说明如何使用 BoxConstraints,并不是一个完整的、可运行的自定义布局实现。
2.4 SizedBox 固定大小的盒子
SizedBox 用于提供一个具有固定大小的盒子,通常用于在布局中添加间距或调整Widget的大小。
Column(
children: <Widget>[
SizedBox(width: 50, height: 50, child: Placeholder()),
// ...其他Widget
],
)
2.5 UnconstrainedBox 移除约束影响
UnconstrainedBox 用于移除父Widget对子Widget的布局约束,允许子Widget自由布局(尽管最终的大小仍然受限于父Widget的可见区域)。
UnconstrainedBox(
child: Container(
width: double.infinity, // 这将不再受限于父Widget的约束
height: double.infinity,
color: Colors.blue,
),
)
请注意,UnconstrainedBox 内部的Widget大小仍然受到其父Widget(通常是屏幕或滚动容器)的限制。
2.6 AspectRatio
AspectRatio 用于保持子Widget的宽高比。
AspectRatio(
aspectRatio: 16 / 9, // 宽高比
child: Placeholder(),
)
2.7 LimitedBox
LimitedBox 用于限制子Widget的最大和/或最小尺寸。
LimitedBox(
maxWidth: 200,
maxHeight: 100,
child: Placeholder(),
)
2.8 FractionallySizedBox
FractionallySizedBox 根据其父Widget的大小和指定的宽度和高度比例来设置子Widget的大小。
FractionallySizedBox(
widthFactor: 0.5, // 宽度占父Widget宽度的50%
heightFactor: 0.3, // 高度占父Widget高度的30%
child: Placeholder(),
)
这些代码片段展示了如何在Flutter中使用这些类和Widget。请注意,在实际应用中,您可能需要根据具体需求调整这些示例。
2.9 层叠、绝对布局
顾名思义,层叠布局就是可以覆在Flutter中,Stack 和 Positioned 通常一起使用来创建一个重叠的子Widget布局。Stack 是一个允许其子Widget在彼此的顶部进行堆叠的Widget,而 Positioned 则用于在 Stack 内部定位子Widget。
2.10 Stack 和 Positioned
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Stack and Positioned Example'),
),
body: Center(
child: Stack(
alignment: Alignment.center, // 可选:设置子Widget的对齐方式(如果未使用Positioned)
children: <Widget>[
// 底层Widget:一个蓝色的圆形
Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
),
// 使用Positioned定位的Widget:一个红色的矩形
Positioned(
top: 50, // 距离Stack顶部的距离
left: 50, // 距离Stack左侧的距离
child: Container(
width: 100,
height: 50,
color: Colors.red,
),
),
// 另一个使用Positioned定位的Widget:一个绿色的矩形
Positioned(
bottom: 20, // 距离Stack底部的距离
right: 20, // 距离Stack右侧的距离
child: Container(
width: 75,
height: 75,
color: Colors.green,
),
),
],
),
),
),
);
}
}
在这个示例中,我们创建了一个 Stack,它包含三个子Widget:
- 一个底层的蓝色圆形
Container。 - 一个使用
Positioned定位的红色矩形Container,它位于蓝色圆形的上方和左侧。 - 另一个使用
Positioned定位的绿色矩形Container,它位于蓝色圆形的下方和右侧。
Positioned Widget 通过 top、bottom、left 和 right 属性来指定它相对于 Stack 容器的边界的距离。这些属性可以是固定的数值,也可以是相对于 Stack 尺寸的百分比(如果使用了 FractionalOffset 或者在布局构建函数中进行了相应的计算)。
注意,虽然 Stack 有一个 alignment 属性,但它通常只在没有使用 Positioned 的情况下才有效。当使用 Positioned 时,每个子Widget的位置都是独立指定的,因此 Stack 的 alignment 属性对这些子Widget没有影响。
2.11 Align 对齐方式
在Flutter中,Align是一个用于根据指定的对齐方式调整其子组件位置的布局组件。它允许开发者在父组件中精确地定位子组件,并支持多种预设的对齐方式,如顶部、底部、左侧、右侧、居中等。以下是关于Align的详细介绍和使用示例:
Align组件的属性
alignment:指定子组件在父组件中的对齐方式。该属性接受一个Alignment对象,该对象有两个属性x和y,取值范围为-1.0到1.0,分别表示相对于父组件宽度和高度的比例。例如,Alignment(-1.0, -1.0)表示左上角对齐,Alignment(1.0, 1.0)表示右下角对齐。Align组件也支持直接使用预设的对齐方式,如Alignment.topLeft、Alignment.center等。widthFactor:指定子组件的宽度相对于父组件宽度的比例因子。取值范围为0.0到1.0。例如,widthFactor: 0.5表示子组件宽度为父组件宽度的一半。heightFactor:指定子组件的高度相对于父组件高度的比例因子。取值范围为0.0到1.0。例如,heightFactor: 0.8表示子组件高度为父组件高度的80%。child:要放置在父组件中的子组件。
示例
以下是一个简单的示例,展示了如何使用Align组件将子组件对齐到父组件的右下角:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Align Example'),
),
body: Container(
color: Colors.grey[200],
width: double.infinity,
height: double.infinity,
child: Align(
alignment: Alignment.bottomRight,
child: Container(
color: Colors.blue,
width: 100,
height: 100,
),
),
),
),
);
}
}
在这个示例中,我们创建了一个Container作为父组件,并设置了一个灰色的背景色。然后,我们使用Align组件将一个蓝色的Container子组件对齐到父组件的右下角。通过设置alignment属性为Alignment.bottomRight,我们实现了这一对齐效果。
2.12 Align 自定义对齐
除了使用预设的对齐方式外,Align组件还支持自定义对齐。通过调整Alignment对象的x和y属性,我们可以将子组件对齐到父组件中的任何位置。例如,以下代码将子组件对齐到父组件的中心偏上位置:
Align(
alignment: Alignment(0, -0.5), // 中心偏上位置
child: Container(
color: Colors.red,
width: 50,
height: 50,
),
)
在这个示例中,我们设置alignment属性为Alignment(0, -0.5),这表示子组件在水平方向上居中对齐,在垂直方向上距离父组件顶部50%的位置(因为y值为-0.5,所以子组件会向上偏移父组件高度的一半)。
综上所述,Align组件是Flutter中一个非常有用的布局组件,它提供了一种简单而强大的方法来对齐和定位子组件。通过合理使用Align组件,我们可以创建出更加灵活和响应式的布局效果。
2.13 Flex(弹性布局)
在Flutter中,Flex 是一个用于在主轴(通常是水平方向)和交叉轴(通常是垂直方向)上排列子Widget的容器。它类似于 Row 和 Column,但提供了更多的灵活性,因为您可以自定义主轴的方向、子Widget的对齐方式、主轴和交叉轴上的间距等。
Flex 的主要属性包括:
direction:主轴的方向,可以是水平(Axis.horizontal)或垂直(Axis.vertical)。mainAxisAlignment:子Widget在主轴上的对齐方式。crossAxisAlignment:子Widget在交叉轴上的对齐方式。mainAxisSpacing和crossAxisSpacing:分别控制子Widget在主轴和交叉轴上的间距(注意:这些属性在Flutter的当前版本中可能不是直接可用的,通常使用spacing或children之间的空白Widget来实现间距)。children:要排列的子Widget列表。
以下是一个使用 Flex 的简单代码片段:
child: Flex(
direction: Axis.horizontal, // 主轴方向为水平
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 子Widget在主轴上均匀分布
crossAxisAlignment: CrossAxisAlignment.center, // 子Widget在交叉轴上居中对齐
children: <Widget>[
Flexible(
flex: 1, // 第一个子Widget占据1份空间
child: Container(
color: Colors.red,
width: 50, // 注意:这里的宽度设置可能不起作用,因为Flexible会根据flex值分配空间
height: 50,
),
),
Flexible(
flex: 2, // 第二个子Widget占据2份空间
child: Container(
color: Colors.green,
width: 50, // 同样,这里的宽度可能不起作用
height: 50,
),
)
],
),
1、在上面的代码中,Flexible widget被用于包裹每个子Container,因为Flex通常与Flexible一起使用来指定子Widget应该如何根据其flex值分配空间。如果您直接使用Container或其他没有flex属性的Widget作为Flex的子Widget,那么这些子Widget将不会根据flex值进行大小调整,而是会尽可能小(通常是其内容的大小)。
2、width属性在Flexible或Flex的直接子Widget中可能不起作用,因为大小是由Flex容器根据其子Widget的flex值和可用空间来决定的。如果您需要为子Widget设置特定的宽度或高度,您可能需要使用SizedBox、AspectRatio或其他布局Widget来进一步控制大小。
3、使用Row或Column可能更为方便:在实际应用中,如果您只是想要一个简单的水平或垂直布局,并且不需要太多的自定义,那么使用Row或Column可能更为方便。Row和Column实际上是Flex的特殊情况,其中direction属性已经被固定为水平或垂直。
2.14 Row(水平、线性布局)
Row的direction属性被固定为Axis.horizontal,是Flex的特殊情况
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Row Example'),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 子Widget在主轴上均匀分布
crossAxisAlignment: CrossAxisAlignment.center, // 子Widget在交叉轴上居中对齐
children: <Widget>[
Icon(Icons.star, color: Colors.red),
Text('Flutter', style: TextStyle(fontSize: 24)),
Icon(Icons.arrow_forward, color: Colors.blue),
],
),
),
),
);
}
}
2.15 Column(垂直、线性布局)
Column的direction属性被固定为Axis.vertical,是Flex的特殊情况
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Column Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, // 子Widget在主轴上居中对齐
crossAxisAlignment: CrossAxisAlignment.start, // 子Widget在交叉轴上靠左对齐
children: <Widget>[
Text('Flutter', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
Text('一个现代的UI工具包,用于从单一代码库构建美观的、原生编译的移动、Web和桌面应用程序。', style: TextStyle(fontSize: 16)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () {},
child: Text('了解更多'),
),
// 你可以在这里添加更多的按钮,并使用 Spacer widget 来添加间距
],
),
],
),
),
),
);
}
}
mainAxisAlignment:
在Row中,主轴是水平方向,因此mainAxisAlignment控制水平对齐,而在Column中,主轴是垂直方向,因此mainAxisAlignment控制垂直对齐。
crossAxisAlignment:
crossAxisAlignment在Row中控制垂直对齐,在Column中控制水平对齐。
2.16 Wrap (流式布局)
前面介绍的 Row 和 Column 都是单行和单列的,不可折行,超出屏幕会被截断。
接下来介绍的Wrap和Flow支持流式布局,内容溢出会自动折行显示,避免显示不全
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wrap Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Wrap Layout Example'),
),
body: Center(
child: Wrap(
spacing: 8.0, // 子元素之间的间距
runSpacing: 4.0, // 子元素行之间的间距
alignment: WrapAlignment.center, // 子元素的对齐方式
children: <Widget>[
Chip(label: Text('Chip 1')),
Chip(label: Text('Chip 2')),
Chip(label: Text('Chip 3')),
Chip(label: Text('Chip 4')),
Chip(label: Text('Chip 5')),
Chip(label: Text('Chip 6')),
// 可以添加更多子元素来测试换行效果
],
),
),
),
);
}
}
在这个Wrap示例中,我们创建了一个包含多个Chip Widget的水平布局。由于使用了Wrap,当一行中的Chip数量达到屏幕宽度限制时,它们会自动换行到下一行。spacing属性控制子Widget之间的垂直间距(在这个例子中实际上由于水平排列所以看起来像是水平间距的效果,但在垂直方向上也会应用),runSpacing属性控制同一行内子Widget之间的水平间距。alignment属性控制子Widget在Wrap容器内的对齐方式。
2.17 Flow (流式布局)
在这个Flow示例中,我们创建了一个自定义的FlowDelegate来控制20个子Widget的布局。Flow本身是一个非常灵活的布局Widget,它允许你通过实现FlowDelegate来自定义子Widget的排列方式。在这个例子中,我们每行排列4个子Widget,并且当一行排满时换行到下一行。子Widget是彩色的容器,里面包含一个数字标签。
请注意,Flow的使用相对复杂一些,因为它需要你手动计算每个子Widget的位置和大小。在上面的例子中,我们使用了简单的数学运算来定位每个子Widget,但在实际应用中,你可能需要更复杂的逻辑来适应不同的布局需求。
2.18 LayoutBuilder 自定义适配布局
前面介绍的Wrap和Flow支持流式布局是显示不下自动换行,但是自定义显示多行,或者多列,什么条件触发都不好自定义。
LayoutBuilder:
LayoutBuilder 是一个用于根据父组件提供的约束(Constraints)动态构建子组件的Widget。它通常用于需要根据可用空间来调整自身大小的场景。LayoutBuilder 接收一个回调函数,该回调函数接收一个BoxConstraints对象作为参数,并返回一个Widget。
也可以根据屏幕大小设计不同的uI,比如iPad上多列展示,iPhone上单列展示+菜单
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// 根据constraints构建Widget
// 例如,根据宽度或高度调整子组件的大小
return Container(
width: constraints.maxWidth * 0.5, // 宽度为父组件宽度的一半
height: constraints.maxHeight * 0.9,
color: Colors.blue,
);
},
)
在这个例子中,LayoutBuilder 根据父组件提供的约束(constraints)来构建一个宽度是父组件一半、颜色为蓝色的Container。
2.19 AfterLayout
当你在组件布局完成后需要回调去做一些事情时,AfterLayout就和适合。
AfterLayout 并不是Flutter框架中的一个直接组件或类。然而,当Widget的布局完成后,可以通过WidgetsBinding.instance.addPostFrameCallback来注册一个回调,这个回调会在当前帧的绘制完成后被调用。这可以用于执行一些需要在布局完成后才能进行的操作,比如获取Widget的实际大小或位置。
WidgetsBinding.instance.addPostFrameCallback((_) {
// 布局完成后执行的代码
// 例如,获取某个Widget的渲染框(RenderBox)并计算其大小或位置
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
print('Widget size: $size');
});
在这个例子中,addPostFrameCallback 用于注册一个回调,该回调会在当前帧的绘制完成后执行。在回调中,我们使用context.findRenderObject()来获取与当前BuildContext关联的RenderBox对象,并打印出其大小。
需要注意的是,context.findRenderObject() 方法通常用于调试或特定情况下,因为它直接依赖于底层的渲染树。在大多数情况下,应该避免在Widget层直接操作渲染树。
三、容器
3.1 Container
Container是一个常用的组件,用于承载其他子组件,并提供布局和装饰功能。它支持设置边距、填充、背景色、边框、圆角等多种属性。
Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(20),
color: Colors.grey,
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(10),
),
child: Text('Container示例'),
)
3.2 Padding(边距)
Padding组件用于为其子组件添加边距或填充。它可以通过EdgeInsets类来设置边距的大小,支持设置所有方向的统一边距、对称方向的边距以及单独方向的边距。
Padding(
padding: EdgeInsets.all(20),
child: Text('Padding示例'),
)
3.3 DecoratedBox(边框、阴影 等)
DecoratedBox组件用于为其子组件添加装饰效果,如背景色、边框、阴影等。它使用BoxDecoration类来定义装饰效果。
DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 8, offset: Offset(0, 4)),
],
),
child: Text('DecoratedBox示例'),
)
BoxDecoration类提供了多种属性来自定义装饰效果,包括颜色、图像、边框、圆角、阴影等。
3.4 Transform(变换)
Transform组件用于对其子组件进行矩阵变换,如平移、旋转和缩放。它使用Matrix4类来定义变换矩阵。
// 平移示例
Transform.translate(
offset: Offset(50, 0),
child: Container(
width: 100,
height: 100,
color: Colors.red,
child: Center(child: Text('平移示例')),
),
)
// 旋转示例
Transform.rotate(
angle: 0.5, // 0.5弧度,即约28.65度
child: Container(
width: 100,
height: 100,
color: Colors.green,
child: Center(child: Text('旋转示例')),
),
)
// 缩放示例
Transform.scale(
scale: 0.5,
child: Container(
width: 100,
height: 100,
color: Colors.yellow,
child: Center(child: Text('缩放示例')),
),
)
3.5 Clip(裁剪)、FittedBox(缩放)
Clip组件用于裁剪其子组件,支持多种裁剪形状,如矩形、圆形等。FittedBox组件则用于根据其父容器的大小来缩放其子组件。
// Clip示例
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: Center(child: Text('Clip示例')),
),
)
// FittedBox示例
FittedBox(
fit: BoxFit.cover, // 缩放方式
child: Image.network(
'https://example.com/image.jpg',
fit: BoxFit.none, // 图片本身的缩放方式,这里不影响FittedBox
),
)
注意:FittedBox通常与图像或其他可缩放内容一起使用,以根据其父容器的大小进行缩放。
3.6 Scaffold(页面结构)
Scaffold是Flutter中用于构建页面结构的组件,它提供了对页面标题、侧边栏、底部导航和浮动按钮等元素的支持。
Scaffold(
appBar: AppBar(
title: Text('Scaffold示例'),
),
body: Center(
child: Text('页面内容'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: '点击我',
child: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: '设置',
),
],
),
)
四、滚动列表
4.1 SingleChildScrollView 可滚动列表
- 概述:SingleChildScrollView是一个可滚动的视图,它只能包含单个子控件。当子控件的高度或宽度超过视口(viewport)时,可以使用SingleChildScrollView来实现滚动。
- 数据量过多不建议使用,可以使用ListView
- 示例:
SingleChildScrollView(
child: Column(
children: <Widget>[
Container(height: 100, color: Colors.red),
Container(height: 100, color: Colors.green),
Container(height: 100, color: Colors.blue),
Container(height: 100, color: Colors.yellow),
],
),
);
4.2 ListView 可滚动列表
- 概述:ListView是最常用的可滚动列表组件之一,用于在一个可滚动的列表中显示一系列的子控件。
- 示例:
ListView(
children: <Widget>[
ListTile(leading: Icon(Icons.map), title: Text('Map')),
ListTile(leading: Icon(Icons.photo_album), title: Text('Album')),
ListTile(leading: Icon(Icons.phone), title: Text('Phone')),
],
);
当需要显示大量数据时,可以使用ListView.builder来避免同时创建所有子控件的问题:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text('Item ${items[index]}'));
},
);
4.3 Slivers 复杂滚动视图基础
- 概述:Slivers是Flutter中用于构建复杂滚动视图的基本构建块。它们通常与CustomScrollView一起使用,以实现自定义滚动效果。SliverAppBar是Slivers的一个常见示例,它用于在滚动视图顶部显示一个可伸缩的应用栏。
- 示例(SliverAppBar):
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Title'),
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://picsum.photos/200/300', fit: BoxFit.cover),
),
),
// 其他Slivers...
],
);
4.4 SliverAppBar
SliverAppBar适用于需要在滚动页面中保持导航栏或标题栏的应用场景,如新闻应用、社交媒体应用等。它可以根据用户的滚动行为自动调整大小,从而提供更好的用户体验和视觉效果。
创建一个带有标题、背景图片和自定义高度的SliverAppBar:
SliverAppBar(
title: Text('SliverAppBar Title'),
backgroundColor: Colors.purpleAccent,
collapsedHeight: 60.0,
expandedHeight: 200.0,
floating: true,
pinned: true,
snap: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Flexible Space Title'),
background: Image.asset('images/head.jpg', fit: BoxFit.fill),
),
)
在这个示例中,我们设置了SliverAppBar的标题、背景颜色、折叠高度、展开高度、浮动、固定、立即展示等属性,并使用FlexibleSpaceBar组件在展开时显示自定义的标题和背景图片。
4.5 AnimatedList 操作动画列表
- 概述:AnimatedList是一个StatefulWidget,它允许在添加或删除列表项时执行动画。
- 示例:
AnimatedList(
key: globalKey,
initialItemCount: data.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: ListTile(
key: ValueKey(data[index]),
title: Text(data[index]),
trailing: IconButton(
onPressed: () {
onDelete(context, index);
},
icon: const Icon(Icons.delete, color: Colors.red, size: 30),
),
),
);
},
);
// 删除和添加的逻辑...
4.6 GridView 网格列表
- 概述:GridView是另一种常用的可滚动列表组件,它将子控件排列成网格布局。
- 示例:
GridView.count(
crossAxisCount: 2,
children: List.generate(8, (index) {
return Container(
color: Colors.blue,
child: Center(child: Text('Item $index')),
);
}),
);
当需要显示大量数据时,可以使用GridView.builder:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
itemCount: items.length,
itemBuilder: (context, index) {
return Container(color: items[index]);
},
);
4.7 PageView 分页
PageView 是 Flutter 中用于实现分页滚动效果的组件。以下是一个简单的 PageView 示例,它展示了三张图片的分页滚动:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('PageView 示例'),
),
body: PageView(
children: [
Image.network('https://picsum.photos/500/300?image=1'),
Image.network('https://picsum.photos/500/300?image=2'),
Image.network('https://picsum.photos/500/300?image=3'),
],
),
),
);
}
}
4.8 TabView 选项卡切换
在 Flutter 中,TabBar 和 TabBarView 通常一起使用来实现选项卡切换效果。以下是一个简单的 TabView 示例:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('TabView 示例'),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car), text: 'Car'),
Tab(icon: Icon(Icons.directions_transit), text: 'Transit'),
Tab(icon: Icon(Icons.directions_bike), text: 'Bike'),
],
),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
注意:在实际应用中,TabBarView 的子组件通常是更复杂的布局或组件,而不仅仅是图标。
4.9 CustomScrollView 自定义的滚动视图
CustomScrollView 允许你创建自定义的滚动视图,它可以包含多种滚动模型,如 SliverList、SliverGrid 和 SliverAppBar 等。以下是一个简单的 CustomScrollView 示例,它结合了 SliverAppBar 和 SliverList:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('CustomScrollView 示例'),
),
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('Custom Scroll View'),
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('Item $index'),
);
},
childCount: 20,
),
),
],
),
),
);
}
}
4.10 NestedScrollView 嵌套滚动视图
NestedScrollView 允许你在滚动视图内部嵌套另一个滚动视图,通常用于实现复杂的滚动布局,如带有头部和可滚动内容的页面。以下是一个简单的 NestedScrollView 示例,它结合了 SliverAppBar 和 Column 内部的 ListView:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('NestedScrollView 示例'),
),
body: NestedScrollView(
headerSliverBuilder: (context, value) {
return <Widget>[
SliverAppBar(
title: Text('Nested Scroll View'),
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
'https://picsum.photos/200/300',
fit: BoxFit.cover,
),
),
pinned: true,
),
];
},
body: Column(
children: <Widget>[
Container(
height: 150,
color: Colors.grey[200],
child: Center(child: Text('Fixed Header')),
),
Expanded(
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
),
),
],
),
),
),
);
}
}
注意:在 NestedScrollView 的 body 中使用 Column 和 Expanded 时,Expanded 确保了 ListView 能够占用剩余的空间并允许其滚动。然而,这个简单的 NestedScrollView 示例可能不会完全按照预期工作,因为 NestedScrollView 通常与 Sliver 组件一起使用以实现更复杂的滚动效果。在实际应用中,你可能需要调整布局或使用 SliverToBoxAdapter 等组件来适应你的需求。
五、动画
在Flutter中,动画是提升用户体验和界面吸引力的关键元素。Flutter提供了一系列强大的动画组件,使得开发者能够轻松地创建各种复杂的动画效果。以下是一些Flutter中常用的动画组件:
5.1、核心动画组件
-
Animation
- 描述:Animation是Flutter动画系统中的核心抽象类,通常使用其子类如AnimationController来创建动画。它保存了动画的插值和状态。
- 功能:可以通过监听其值的变化来更新UI,实现动画效果。
-
AnimationController
- 描述:AnimationController是动画控制器,用于控制动画的开始、停止、反向播放等。
- 功能:它继承自Animation,并提供了forward()、stop()、reverse()等方法来控制动画的播放。
-
Curve
- 描述:Curve决定了动画执行的曲线,即动画变化的速率。
- 功能:Flutter提供了多种预置的动画曲线,如线性(Curves.linear)、缓入(Curves.easeIn)、缓出(Curves.easeOut)等,也可以通过自定义Curve来实现特定的动画效果。
-
Tween
- 描述:Tween用于映射生成不同范围的值。因为AnimationController的动画值是double类型的,范围在0到1之间,所以Tween可以将这个值映射到其他范围,如颜色、尺寸等。
- 功能:它提供了begin和end属性来指定映射的起始值和结束值,并通过animate()方法与AnimationController关联。
5.2、常用动画过渡组件
-
FadeTransition
- 描述:用于对透明度使用动画的组件。
- 功能:通过改变opacity属性来实现淡入淡出的动画效果。
-
ScaleTransition
- 描述:用于对尺寸进行动画的组件。
- 功能:通过改变scale属性来实现缩放动画效果。
-
SizeTransition
- 描述:用于对尺寸进行动画的组件,与ScaleTransition类似,但更灵活。
- 功能:可以通过设置axisAlignment和animation属性来控制动画的方向和效果。
-
RotationTransition
- 描述:用于对旋转进行动画的组件。
- 功能:通过改变turns属性来实现旋转动画效果。
-
SlideTransition
- 描述:用于对位置进行动画的组件。
- 功能:通过改变position属性来实现滑动动画效果。
-
AnimatedContainer
- 描述:AnimatedContainer是Container的动画版本,可以通过修改动画过程中的尺寸、颜色、对齐方式等属性来实现容器的动画效果。
- 功能:它提供了duration、curve等属性来控制动画的时长和曲线。
-
AnimatedCrossFade
- 描述:用于在两个子组件之间进行交叉淡入淡出的动画过渡。
- 功能:通过crossFadeState属性来控制当前显示的子组件,并通过duration属性来控制动画的时长。
5.3、其他动画组件
-
AnimatedBuilder
- 描述:AnimatedBuilder是一个用于构建动画的通用组件。
- 功能:它可以将动画效果和组件分离,使得动效可以应用于不同的组件。通过builder属性传入一个构建函数,该函数根据animation的值来构建组件。
-
Hero
- 描述:Hero动画用于在两个页面或组件之间共享和过渡相同的元素。
- 功能:当元素从一个页面跳转到另一个页面时,Hero动画会保持该元素的外观和状态,并创建平滑的过渡效果。
-
AnimatedDefaultTextStyle
- 描述:用于文字样式动画效果的组件。
- 功能:它允许开发者在动画过程中改变文字的样式,如字体大小、颜色等。
这些动画组件可以单独使用,也可以组合使用来创建更复杂的动画效果。通过合理地使用这些动画组件,开发者可以显著提升应用的用户体验和视觉效果。
5.4: 示例
5.4.1 使用 AnimationController 和 Tween 实现颜色渐变
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Color Animation')),
body: ColorAnimationDemo(),
),
);
}
}
class ColorAnimationDemo extends StatefulWidget {
@override
_ColorAnimationDemoState createState() => _ColorAnimationDemoState();
}
class _ColorAnimationDemoState extends State<ColorAnimationDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true); // 反复播放动画,反向也播放
_animation = ColorTween(
begin: Colors.red,
end: Colors.blue,
).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
width: 100,
height: 100,
color: Colors.red, // 初始颜色,动画开始时会被覆盖
),
builder: (context, child) {
return Container(
width: 100,
height: 100,
color: _animation.value,
);
},
),
);
}
}
5.4.2: 使用 FadeTransition 实现淡入淡出
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Fade Transition')),
body: FadeTransitionDemo(),
),
);
}
}
class FadeTransitionDemo extends StatefulWidget {
@override
_FadeTransitionDemoState createState() => _FadeTransitionDemoState();
}
class _FadeTransitionDemoState extends State<FadeTransitionDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..forward(); // 开始播放动画
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: FadeTransition(
opacity: _animation,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
);
}
}
5.4.3: 使用 ScaleTransition 实现缩放
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Scale Transition')),
body: ScaleTransitionDemo(),
),
);
}
}
class ScaleTransitionDemo extends StatefulWidget {
@override
_ScaleTransitionDemoState createState() => _ScaleTransitionDemoState();
}
class _ScaleTransitionDemoState extends State<ScaleTransitionDemo> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..forward(); // 开始播放动画
_animation = Tween<double>(begin: 0.5, end: 1.5).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: ScaleTransition(
scale: _animation,
alignment: Alignment.center,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
);
}
}