Flutter 38~49课总结
1. Flutter中一切皆Widget
2. Widget即对象
var tempList = listData.map((value) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: const Color.fromRGBO(233, 233, 233, 0.9), width: 1)),
child: Column(
children: <Widget>[
Image.network(value['imageUrl']),
const SizedBox(height: 12),
Text(
value['title'],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
)
],
),
);
});
const element = (
<div className="container">
<Welcome name="Alice" />
<p>JSX 本质是 JavaScript 对象</p>
</div>
)
Flutter中的路由
Flutter为在屏幕之间导航和处理深度链接提供了一个完整的系统。
在 Flutter 中,屏 (screen) 和 页面 (page) 都叫做 路由 (route),在下文中统称为“路由 (route)”。
在Flutter中通过Navigator组件管理路由导航。
提供了管理堆栈的方法。
- Navigator.push
- Navigator.pop
Flutter中给我们提供了两种配置路由跳转的方式:
- 基本路由
- 命名路由
基本路由跳转
void main() {
runApp(
MaterialApp(
title: '基本路由',
home: FirstRoute()
),
);
}
class FirstRoute extends StatelessWidget {
const FirstRoute({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('First Route')),
body: Center(
child: ElevatedButton(
child: const Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
},
),
),
);
}
}
重要Wedgit
- Navigator:通过
stack
规则来管理一组Widget
。
Future<T?> push<T extends Object?>( BuildContext context, Route<T> route )
- MaterialPageRoute: 根据平台和参数提供不同的过渡动画:
- Android:默认从右侧滑入,退出时从右侧滑出。
- iOS:默认从底部滑入(类似模态对话框),退出时从底部滑出。
- 设置 fullscreenDialog: true:在 Android 和 iOS 上均使用从底部滑入的动画。
Inheritance
Object Route<T> OverlayRoute<T> TransitionRoute<T> ModalRoute<T> PageRoute<T> MaterialPageRoute
- SecondRoute: 指定跳转的页面类
路由返回
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Second Route')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Go back!'),
),
),
);
}
}
路由跳转传值
// 方式一:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: '参数'),
),
);
// 方式二:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailScreen(),
settings: RouteSettings(arguments: '参数'),
),
);
// 接受页面从构造函数参数获取
重要Widget
- DetailScreen:通过给跳转页面的类的构造函数传值的方式来传递参数
- RouteSettings:在构造 Route 时可能有用的数据。
路由返回传值
// 传递方
onPressed: () {
Navigator.pop(context, 'Yep!');
},
// 接收方
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
命名路由定义
void main() {
runApp(
MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => const FirstScreen(),
'/second': (context) => const SecondScreen(),
},
),
);
}
路由跳转
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('First Screen')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/second');
},
child: const Text('Launch screen'),
),
),
);
}
}
命名路由传参
// 传递单个参数
Navigator.pushNamed(context, '/user', arguments: 123);
// 传递多个参数(使用 Map)
Navigator.pushNamed(
context,
'/product',
arguments: {
'id': 456,
'name': 'iPhone',
'price': 999.99,
}
);
路由替换
- 即将跳转的路由来替换当前的路由
- pushReplacementNamed
Navigator.of(context).pushReplacementNamed('/registerSecond');
返回到根路由
- 从多层的嵌套路由返回到根路由
- pushAndRemoveUntil
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) {
return const MyHomePage();
}), (route) => false);
Future<T?> pushAndRemoveUntil<T extends Object?>( BuildContext context, Route<T> newRoute, RoutePredicate predicate )
- newRoute:要导航到的目标路由,通常使用 MaterialPageRoute、CupertinoPageRoute 或自定义路由。
- predicate:一个返回布尔值的回调函数,用于判断哪些路由需要被保留。Flutter 会从当前路由开始逐个检查历史路由,直到找到第一个满足 predicate 返回 true 的路由,然后移除该路由之前的所有路由。
Dialog
- Dialog 是一种用于在当前页面上显示临时 UI 的组件,通常用于向用户呈现重要信息、获取确认或收集少量输入。它会创建一个浮动在当前内容之上的模态窗口,吸引用户的注意力并阻止背景交互,直到被关闭。
- 具象弹框:
- AlertDialog
- SimpleDialog
- 抽象弹框:
- Dialog
- showModalBottomSheet
AlertDialog
- 一个
Material Design
设计系统下的警告对话框,警告对话框(也称为基本对话框)通知用户需要确认的情况。
showDialog
用法
import 'package:flutter/material.dart';
void main() => runApp(const AlertDialogExampleApp());
class AlertDialogExampleApp extends StatelessWidget {
const AlertDialogExampleApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('AlertDialog Sample')),
body: const Center(child: DialogExample()),
),
);
}
}
class DialogExample extends StatelessWidget {
const DialogExample({super.key});
Widget build(BuildContext context) {
return TextButton(
onPressed:
() => showDialog<String>(
context: context,
builder:
(BuildContext context) => AlertDialog(
title: const Text('AlertDialog Title'),
content: const Text('AlertDialog description'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
),
child: const Text('Show Dialog'),
);
}
}
SimpleDialog
SimpleDialog
一个Material Design
设计系统下的一个简单的对话框,它为用户提供几个选项之间的选择。一个简单的对话框有一个可选的标题,显示在选项的上方。
import 'package:flutter/material.dart';
class SimpleExample extends StatelessWidget {
const SimpleExample({super.key});
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
final String? result = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('Select assignment'),
children: <Widget>[
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Treasury');
},
child: const Text('Treasury department'),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'State');
},
child: const Text('State department'),
),
],
);
});
if (result != null) {
print('Selected department: $result');
}
},
child: const Text('弹出SimpleDialog'),
);
}
}
SimpleDialogOption
SimpleDialog 中使用的一个选项
关闭对话框
showModalBottomSheet
底部弹出对话框
用法
import 'package:flutter/material.dart';
void main() => runApp(const BottomSheetApp());
class BottomSheetApp extends StatelessWidget {
const BottomSheetApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Bottom Sheet Sample')),
body: const BottomSheetExample(),
),
);
}
}
class BottomSheetExample extends StatelessWidget {
const BottomSheetExample({super.key});
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
},
);
},
),
);
}
}
自定义Dialog
内置的Dialog
有自己的固定布局模板,无法做更多的自定义。
- 直接使用
Dialog Widget
开发自定义对话框
- 我们需要通过继承
Dialog
类来实现自定义Dialog
。
弹出自定义Dialog
PageView
PageView 是 Flutter 中用于创建水平或垂直滚动分页效果的组件,常用于轮播图、引导页、图片浏览等场景,本质是一个分页滚动的列表容器。
基本用法
- 通过实例化
PageView
类,传递构造函数的配置参数来使用;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(const PageViewExampleApp());
class PageViewExampleApp extends StatelessWidget {
const PageViewExampleApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('PageView Sample')),
body: const PageViewExample(),
),
);
}
}
class PageViewExample extends StatefulWidget {
const PageViewExample({super.key});
State<PageViewExample> createState() => _PageViewExampleState();
}
class _PageViewExampleState extends State<PageViewExample> with TickerProviderStateMixin {
late PageController _pageViewController;
late TabController _tabController;
int _currentPageIndex = 0;
void initState() {
super.initState();
_pageViewController = PageController();
_tabController = TabController(length: 3, vsync: this);
}
void dispose() {
super.dispose();
_pageViewController.dispose();
_tabController.dispose();
}
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
PageView(
controller: _pageViewController,
onPageChanged: _handlePageViewChanged,
children: <Widget>[
Center(child: Text('First Page', style: textTheme.titleLarge)),
Center(child: Text('Second Page', style: textTheme.titleLarge)),
Center(child: Text('Third Page', style: textTheme.titleLarge)),
],
),
PageIndicator(
tabController: _tabController,
currentPageIndex: _currentPageIndex,
onUpdateCurrentPageIndex: _updateCurrentPageIndex,
isOnDesktopAndWeb: _isOnDesktopAndWeb,
),
],
);
}
void _handlePageViewChanged(int currentPageIndex) {
if (!_isOnDesktopAndWeb) {
return;
}
_tabController.index = currentPageIndex;
setState(() {
_currentPageIndex = currentPageIndex;
});
}
void _updateCurrentPageIndex(int index) {
_tabController.index = index;
_pageViewController.animateToPage(
index,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
bool get _isOnDesktopAndWeb =>
kIsWeb ||
switch (defaultTargetPlatform) {
TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => true,
TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia => false,
};
}
- 通过调用
PageView
类的builder
静态方法创建PageView
实例,且页面的创建是按需的
上拉无线加载功能
参数名 | 描述 | 类型 |
---|---|---|
onPageChanged | 换页监听事件 | ValueChanged<int> |
无缝轮播图
通过SizedBox
组件作为PageView
组件的容器,控制宽高。
轮播图的指示灯
通过Container
创建元素,配合Positioned
布局组件来定位元素。注意定位元素要再Stack
组件中使用,因为定位涉及到层的堆栈。
PageView实现动态轮播图
- 定时器
- PageController:
PageView
的控制器。控制哪一个页面展示再PageView
中。通过animateToPage
方法控制页面索引。
class _PageViewExampleState extends State<PageViewExample> with TickerProviderStateMixin {
late PageController _pageViewController;
late TabController _tabController;
int _currentPageIndex = 0;
void initState() {
super.initState();
_pageViewController = PageController();
_tabController = TabController(length: 3, vsync: this);
}
void dispose() {
super.dispose();
_pageViewController.dispose();
_tabController.dispose();
}
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
PageView(
controller: _pageViewController,
onPageChanged: _handlePageViewChanged,
children: <Widget>[
Center(child: Text('First Page', style: textTheme.titleLarge)),
Center(child: Text('Second Page', style: textTheme.titleLarge)),
Center(child: Text('Third Page', style: textTheme.titleLarge)),
],
),
PageIndicator(
tabController: _tabController,
currentPageIndex: _currentPageIndex,
onUpdateCurrentPageIndex: _updateCurrentPageIndex,
isOnDesktopAndWeb: _isOnDesktopAndWeb,
),
],
);
}
}
AutomaticKeepAliveClientMixin
PageView
页面每次切换都会销毁每页的子元素,如果想缓存下来,需要使用
Flutter Key
键是 Widgets, Elements and SemanticsNodes的标识符,不可重复。(DOMid选择器) Key 的子类应该是 LocalKey 或 GlobalKey 的子类。
作用是:
- 保存状态
- 排序
LocalKey
当前组件初始化创建的List
组件有用。
GlobalKey
销毁后再次创建的组件状态有用。
GlobalKey获取子元素,修改子元素
AnimatedList
带有动效的列表。在增删的时候给元素添加动效。
itemBuilder
FadeTransition
import 'package:flutter/material.dart';
class AnimatedListPage extends StatefulWidget {
const AnimatedListPage({super.key});
State<AnimatedListPage> createState() => _AnimatedListPageState();
}
class _AnimatedListPageState extends State<AnimatedListPage> {
final globalKey = GlobalKey<AnimatedListState>();
List<String> list = ["第一条数据", "第二条数据"];
void initState() {
// TODO: implement initState
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
list.add("这是一个数据");
globalKey.currentState!.insertItem(list.length - 1);
},
child: const Icon(Icons.add),
),
appBar: AppBar(
title: const Text("AppBar组件"),
),
body: AnimatedList(
key: globalKey,
initialItemCount: list.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: ListTile(
title: Text(list[index]), trailing: Icon(Icons.delete)),
);
}),
);
}
}
insertItem
Flutter中的动画
- 隐式动画
- 显示动画
Flutter隐式动画
动画背后的实现原理和繁琐的操作细节都被隐藏了,所以叫隐式动画。
- AnimatedContainer
- AnimatedPadding
- AnimatedAlign: 动画版本的 Align,自动转换位置在给定的时间内,只要给定的对齐方式发生变化
import 'package:flutter/material.dart';
class AnimatedContainerExampleApp extends StatelessWidget {
const AnimatedContainerExampleApp({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AnimatedContainer Sample')),
body: const AnimatedContainerExample(),
);
}
}
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool selected = false;
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Center(
child: AnimatedContainer(
width: selected ? 200.0 : 100.0,
height: selected ? 100.0 : 200.0,
color: selected ? Colors.red : Colors.blue,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
);
}
}
Flutter显式动画
能够参与的动画过程叫做显示动画。通过AnimationController
控制动画的暂停,播放,倒放等
- ScaleTransition
- PositionedTransition:Positioned 的动画版本采用了特定的
Animation<RelativeRect>
在动画的整个生命周期内将子对象的位置从起始位置转换到结束位置。 - SizeTransition
import 'package:flutter/material.dart';
void main() => runApp(const ScaleTransitionExampleApp());
class ScaleTransitionExampleApp extends StatelessWidget {
const ScaleTransitionExampleApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(home: ScaleTransitionExample());
}
}
class ScaleTransitionExample extends StatefulWidget {
const ScaleTransitionExample({super.key});
State<ScaleTransitionExample> createState() => _ScaleTransitionExampleState();
}
class _ScaleTransitionExampleState extends State<ScaleTransitionExample>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScaleTransition(
scale: _animation,
child: const Padding(padding: EdgeInsets.all(8.0), child: FlutterLogo(size: 150.0)),
),
),
);
}
}
执行动画
- forward: 正序执行一次
- stop:停止动画
- reset:重置动画
- reverse:倒序执行一次