1 Android界面渲染流程UI树与FlutterUI树的设计思路对比
1.1 android渲染流程中的树的组织
1.1.1 XML加载与解析
1.1.2 ViewRootImpl组织树结构
1.1.3 编舞者掌控调用时机
1.1.4 View负责UI渲染
1.1.5 底层surfacefiling负责沟通硬件
1.2 flutter组件设计思路,无状态与有状态
1.2.1 声明式UI没有XML数据加载解析流程
1.2.2 区别
1.自己组织树结构
2.数据状态的变动
2 Widget组件生命周期详解
2.1 说明
2.1.1 在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget。
2.1.2 StatelessWidget是无状态组件,状态不可变的widget
2.1.3 StatefulWidget是有状态组件,持有的状态可能在widget生命周期改变。
2.1.4 如果需要在生命周期中改变页面中的数据,需要用到StatefulWidget
2.2 Widget组件生命周期
2.2.1 说明
和其他的视图框架比如android的Activity一样,flutter中的视图Widget也存在生命周期,生命周期的回调函数体现在了State上面。组件State的生命周期整理如下图所示:
2.2.2 图例
2.3 生命周期函数说明
2.3.1 framework中对于生命周期函数
2.3.2 createState
当一个StatefulWidget插入到渲染树结构、或者从渲染树结构移除时,都会调用StatefulWidget.createState方法,从而达到更新UI的效果
2.3.3 initState
initState是StatefulWidget创建后调用的第一个方法,而且只执行一次。在执行initState时,View没有渲染,但是StatefulWidget 已经被加载到渲染树里了
2.3.4 didChangeDependencies
didChangeDependencies会在initState后立即调用,当StatefulWidget依赖的InheritedWidget发生变化之后,didChangeDependencies会调用,所以didChangeDependencies可以调用多次
2.3.5 build
build方法会在didChangeDeoendencies之后立即调用,在之后setState()刷新时,会重新调用build绘制页面,所以build方法可以调用多次。但一般不再build中创建除创建Widget的方法,否则会影响渲染效率。
2.3.6 setState
[State] 对象可以通过调用它们的 [setState] 方法自发地请求重建其子树,这表明它们的某些内部状态已经改变,可能会影响该子树中的用户界面。setState方法会被多次调用
2.3.7 didUpdateWidget
1、当调用setState更新UI的时候,都会调用didUpdateWidget。
2、框架在调用 [didUpdateWidget] 之后总是调用 [build],在 [didUpdateWidget] 中对 [setState] 的任何调用都是多余的
2.3.8 deactivate
1、当框架从树中移除此 State 对象时将会调用此方法,
2、在某些情况下,框架将重新插入 State 对象到树的其他位置(例如,如果包含该树的子树 State 对象从树中的一个位置移植到另一位置),框架将会调用 build 方法来提供 State 对象适应其在树中的新位置。
2.3.9 dispose
当框架从树中永久移除此 State 对象时将会调用此方法,与 deactivate的区别是,deactivate 还可以重新插入到树中,而 dispose 表示此 State 对象永远不会在 build。调用完 dispose后,mounted 属性被设置为 false,也代表组件生命周期的结束,此时再调用 setState 方法将会抛出异常。子类重写此方法,释放相关资源,比如动画等
2.4 生命周期调用
2.4.1 说明
假设我们从A页面跳转到B页面, 那么A,B页面的生命周期会是怎样的呢?
B页面进入初始化状态,依次执行4个函数:构造函数 > initState > didChangeDependencies > Widget build , 此时页面加载完成,进入运行态。
此时A页面依次执行deactivate > build函数。注意 此时A页面并未卸载。
然后我们假设B页面只有一个按钮,点击B页面中的按钮,改变按钮的文字,会执行widget的build方法 ,(理论上也应该执行didUpdateWidget,但我这里没有)。
这时,我们点击返回键从B页面返回到A页面。
A页面重新显示,B页面开始卸载。
那么A先执行deactivate > build , 然后B页面依次执行:deactivate > dispose 。
此时A页面进入运行态,B页面移除
2.4.2 代码
import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/DemoPages.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key}); // This widget is the root of your application.
@override Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: const Demo(),);
}
}
class Demo extends StatefulWidget {
const Demo({Key? key}) : super(key: key);
@override State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
@override Widget build(BuildContext context) {
print("------main--------build");
return Scaffold(appBar: AppBar(title: const Text("StatefulWidget demo"),),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ ElevatedButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (setting) {
return const DemoPage();
}));
}, child: const Text("页面跳转"),),
],),),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
print("调用....");
});
},
tooltip: 'Increment',
child: const Icon(Icons
.add),), // This trailing comma makes auto-formatting nicer for build methods.
);
}
@override void initState() {
// implement initState
super.initState();
print("------main--------initState");
}
@override void didChangeDependencies() {
// implement didChangeDependencies
super.didChangeDependencies();
print("-------main-------didChangeDependencies");
}
@override void setState(VoidCallback fn) {
// implement setState
super.setState(fn);
print("------main--------setState");
}
@override void deactivate() {
// implement deactivate
super.deactivate();
print("------main--------deactivate");
}
@override void dispose() {
// implement dispose
super.dispose();
print("------main--------dispose");
}
}
import 'package:flutter / material.dart';
class DemoPage extends StatelessWidget {
const DemoPage({Key? key }) : super(key: key);
@override Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Demo页面"),), body: const DemoWidget(),);
}
}
class DemoWidget extends StatefulWidget {
const DemoWidget({Key? key}) : super(key: key);
@override State<DemoWidget> createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget> {
String btnText = "test";
@override Widget build(BuildContext context) {
print("------_DemoWidgetState--------build");
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ ElevatedButton(onPressed: () {
setState(() {
if (btnText == "test") {
btnText = "测试";
} else {
btnText = "test";
}
});
}, child: Text(btnText),), ElevatedButton(onPressed: () {
print("返回......");
Navigator.of(context).pop();
}, child: const Text("返回"),),
],),);
}
@override void initState() {
// implement initState
super.initState();
print("------_DemoWidgetState--------initState");
}
@override void didChangeDependencies() {
// implement didChangeDependencies
super.didChangeDependencies();
print("-------_DemoWidgetState-------didChangeDependencies");
}
@override void setState(VoidCallback fn) {
// implement setState
super.setState(fn);
print("------_DemoWidgetState--------setState");
}
@override void deactivate() {
// implement deactivate
super.deactivate();
print("------_DemoWidgetState--------deactivate");
}
@override void dispose() {
// implement dispose
super.dispose();
print("-----_DemoWidgetState---------dispose");
}
}
3 Flutter 页面间数据传递(共享)的几种常用方式
3.1 通过构造器(constructor)传递数据
3.1.1 说明
通过构造器传递数据是一种最简单的方式,也是最常用的方式,在第一个页面,我们模拟创建一个我们需要传递数据的对象。当点击跳转的时候,我们把数据传递给DataTransferByConstructorPage页面,并把携带过来的数据展示到页面上
3.1.2 示例
//1.创建一个传递数据对象
final data = TransferDataEntity("001", "张三丰", 18);
//2.定义一个跳转到DataTransferByConstructorPage页面的方法
_transferDataByConstructor(BuildContext context, TransferDataEntity data) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DataTransferByConstructorPage(data: data)));
}
//3.在DataTransferByConstructorPage页面接收到数据并展示出来
//通过构造器的方式传递参数
class DataTransferByConstructorPage extends StatelessWidget {
final TransferDataEntity data;
DataTransferByConstructorPage({this.data});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("构造器方式"),
),
body: Column(
children: <Widget>[
Container(
width: double.infinity,
height: 40.0,
alignment: Alignment.center,
child: Text(data.id),
),
Container(
width: double.infinity,
height: 40.0,
alignment: Alignment.center,
child: Text(data.name),
),
Container(
width: double.infinity,
height: 40.0,
alignment: Alignment.center,
child: Text("${data.age}"),
)
],
),
);
}
}
1.提供一个final变量 final TransferDataEntity data
2.提供一个构造器接收参数 DataTransferByConstructorPage({this.data});
3.2 当一个页面关闭时携带数据到上一个页面(Navigator.pop)
3.2.1 说明
在Android开发中我们需要将数据传递给上一个页面通常使用的传统方式是startActivityForResult()方法。但是在flutter就不用这么麻烦了。只需要使用Navigator.pop方法即可将数据结果带回去。但是我们跳转的时候需要注意两点:
3.2.2 示例
1.我们需要定义一个异步方法用于接收返回来的结果
///跳转的时候我们需要使用异步等待回调结果 dataFromOtherPage 就是返回的结果 _toTransferForResult(BuildContext context, TransferDataEntity data) async {
final dataFromOtherPage = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => TransferRouterPage(data: data)),
) as TransferDataEntity;
}
2.在我们要关闭的页面使用Navigator.pop 返回第一个页面
//返回并携带数据
_backToData(BuildContext context) {
var transferData = TransferDataEntity("嘻嘻哈哈", "007", 20);
Navigator.pop(context, transferData);
}
3.3 InheritedWidget方式
3.3.1 说明
官网给出的解释:InheritedWidget是Flutter中非常重要的一个功能型Widget,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。
InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在Widget树中数据传递方向是从上到下的,这和Notification的传递方向正好相反。
优点:可以控制每个Widget单独去取数据并使用
如果一个页面只存在同一层级的Weiget这时候使用构造器的方式当然是最简单的,也是最方便的,但是如果一个页面存在多个层级的Weiget这时候构造器的方法就有了局限性,这时候我们使用InheritedWidget 是一个比较好的选择。
3.3.2 示例
使用InheritedWidget方式如下几步
3.4 全局的提供数据的方式
3.4.1 说明
这种方式我们还是使用InheritedWidget,区别就是我们不是跳转的时候去创建IGenericDataProvider。而是把他放在最顶层 注意:这种方式一定要把数据放在顶层
3.4.2 示例
(1)定义顶部数据
class MyApp extends StatelessWidget {
// This widget is the root of your application.
// 传递值的数据
var params = InheritedParams();
@override
Widget build(BuildContext context) {
return IGenericDataProvider(
data: params,
child: MaterialApp(
title: 'Data Transfer Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Data Transfer Demo'),
),
);
}
}
(2)接收数据的方式基本和 InheritedWidget相同
final data = IGenericDataProvider.of<TransferDataEntity>(context),获取数据
(3)使用
class InheritedParamsPage extends StatefulWidget {
@override
_InheritedParamsPageState createState() => _InheritedParamsPageState();
}
class _InheritedParamsPageState extends State<InheritedParamsPage> {
@override
Widget build(BuildContext context) {
final data = IGenericDataProvider.of<TransferDataEntity>(context);
return Scaffold(
appBar: AppBar(
title: Text("通过全局数据方式"),
),
body: Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${data.age}"),
),
],
),
);
}
}
3.5 通过全局单例模式来使用
3.5.1 说明
这种方式就是创建一个全局单例对象,任何地方都可以操控这个对象,存储和取值都可以通过这个对象
3.5.2 示例
//创建单例对象
class TransferDataSingleton {
static final TransferDataSingleton _instanceTransfer =
TransferDataSingleton.__internal();
TransferDataEntity transData;
factory TransferDataSingleton() {
return _instanceTransfer;
}
TransferDataSingleton.__internal();
}
final transSingletonData = TransferDataSingleton();
//给单例对象存放数据
_singletonDataTransfer(BuildContext context) {
var transferData = TransferDataEntity("二汪", "002", 25);
transSingletonData.transData = transferData;
Navigator.push(context,
MaterialPageRoute(builder: (context) => TransferSingletonPage()));
}
//接收并使用传递的值
class TransferSingletonPage extends StatefulWidget {
@override
_TransferSingletonPageState createState() => _TransferSingletonPageState();
}
class _TransferSingletonPageState extends State<TransferSingletonPage> {
@override
Widget build(BuildContext context) {
//直接引入单例对象使用
var data = transSingletonData.transData;
return Scaffold(
appBar: AppBar(
title: Text("全局单例传递数据"),
),
body: Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${data.age}"),
),
],
),
);
}
}
3.6 全局单例结合Stream的方式传递数据
3.6.1 说明
创建一个接受全局的单例对象,并把传递值转成Stream方式
在接收数据可以使用StreamBuilder直接接收并处理
3.6.2 示例
//单例模式
class TransferStreamSingleton {
static final TransferStreamSingleton _instanceTransfer =
TransferStreamSingleton.__internal();
StreamController streamController;
void setTransferData(TransferDataEntity transData) {
streamController = StreamController<TransferDataEntity>();
streamController.sink.add(transData);
}
factory TransferStreamSingleton() {
return _instanceTransfer;
}
TransferStreamSingleton.__internal();
}
final streamSingletonData = TransferStreamSingleton();
// 传递要携带的数据
_streamDataTransfer(BuildContext context) {
var transferData = TransferDataEntity("三喵", "005", 20);
streamSingletonData.setTransferData(transferData);
Navigator.push(
context, MaterialPageRoute(builder: (context) => TransferStreamPage()));
}
// 接收要传递的值
class TransferStreamPage extends StatefulWidget {
@override
_TransferStreamPageState createState() => _TransferStreamPageState();
}
class _TransferStreamPageState extends State<TransferStreamPage> {
StreamController _streamController = streamSingletonData.streamController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("全局单例结合Stream"),
),
body: StreamBuilder(
stream: _streamController.stream,
initialData: TransferDataEntity("", "", 0),
builder: (context, snapshot) {
return Column(
children: <Widget>[
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(snapshot.data.name),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text(snapshot.data.id),
),
Container(
alignment: Alignment.center,
height: 40.0,
child: Text("${snapshot.data.age}"),
),
],
);
}));
}
@override
void dispose() {
_streamController.close();
super.dispose();
}
}
4 跨组件状态共享
4.1 Steam
4.1.1 说明
steam主要目的就是类似于自己建立一个android体系中的Boudle,利用一个FD资源处理
4.1.2 示例
import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/DemoPages.dart';
import 'package:flutter_chapter_04/src/pages/FirstPage.dart';
import 'package:flutter_chapter_04/src/pages/HeartProvide.dart';
import 'package:provide/provide.dart';
import '../data_share/transfer_data_entity.dart';
import '../data_share/transfer_stream_singleton.dart';
void main() {
runApp(const MyApp());
}
var data = ProvideData();
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(
key: key); // This widget is the root of your application.
@override Widget build(BuildContext context) {
var transferData = TransferDataEntity();
streamSingletonData.setTransferData(transferData);
return MaterialApp(title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,),
home: ShareDataWidget(data: data, child: const FirstPage(),),);
}
}
class FirstPage extends StatefulWidget {
const FirstPage({Key key}) : super(key: key);
@override FirstPageState createState() => FirstPageState();
}
class FirstPageState extends State<FirstPage> {
final StreamController _streamController = streamSingletonData
.streamController;
@override Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text("第一个页面"),),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('阅读人数:$num', style: const TextStyle(fontSize: 30.0),),
StreamBuilder(stream: _streamController.stream,
initialData: TransferDataEntity(),
builder: (context, data) {
return IconButton(icon: data.data.isCheck
? const Icon(Icons.favorite, size: 50)
: const Icon(Icons.favorite_border, size: 50), onPressed: () {
setState(() {
data.data.isCheck = data.data.isCheck ? false : true;
print("data-first:${data.data}");
streamSingletonData.setTransferData(data.data);
});
},);
},),
],),),
floatingActionButton: FloatingActionButton(onPressed: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) => const SecondPage()),);
setState(() {
print("更新UI......");
});
}, child: const Icon(Icons.arrow_right),),);
}
}
class SecondPage extends StatefulWidget {
const SecondPage({Key key}) : super(key: key);
@override SecondPageState createState() => SecondPageState();
}
class SecondPageState extends State<SecondPage> {
SecondPageState();
final StreamController _streamController = streamSingletonData
.streamController;
@override Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text("第二个页面"),),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'这是第二个页面', style: TextStyle(fontSize: 30.0, color: Colors.red),),
StreamBuilder(stream: _streamController.stream,
initialData: TransferDataEntity(),
builder: (context, data) {
return IconButton(icon: data.data.isCheck
? const Icon(Icons.favorite, size: 50)
: const Icon(Icons.favorite_border, size: 50), onPressed: () {
setState(() {
data.data.isCheck = data.data.isCheck ? false : true;
print("********data:${data.data}");
streamSingletonData.setTransferData(data.data);
});
},);
},)
],),),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {});
}, child: const Icon(Icons.add),),);
}
@override void dispose() {
_streamController.close();
super.dispose();
}
}
4.2 Flutter Provides
4.2.1 说明
第三方组件应用
4.2.2 示例
import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/FirstPage.dart';
import 'package:provide/provide.dart';
import '../pages/HeartProvide.dart';
void main() {
//初始化
var myProvide = MyProvide();
var providers = Providers(); //将myProvide加到providers中
providers.provide(Provider.function((context) => myProvide));
runApp(ProviderNode(providers: providers, child: MyApp(),));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Provide',
theme: ThemeData(primarySwatch: Colors.blue,),
home: const FirstProvidePage(),);
}
}
class FirstProvidePage extends StatefulWidget {
const FirstProvidePage({Key key}) : super(key: key);
@override FirstProvidePageState createState() => FirstProvidePageState();
}
class FirstProvidePageState extends State<FirstProvidePage> {
@override Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text("第一个页面"),),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Provide<MyProvide>(builder: (context, child, provide) {
return Text(
'阅读人数:' + '${provide.getNum}', style: TextStyle(fontSize: 30.0),);
},),
Provide<MyProvide>(builder: (context, child, provide) {
return IconButton(icon: Provide
.value<MyProvide>(context)
.getIsFavorite
? const Icon(Icons.favorite, size: 50)
: const Icon(Icons.favorite_border, size: 50), onPressed: () {
Provide.value<MyProvide>(context).changeFavorite(!Provide
.value<MyProvide>(context)
.getIsFavorite);
},);
},),
],),),
floatingActionButton: FloatingActionButton(onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondProvidePage()),);
}, child: const Icon(Icons.arrow_right),),);
}
}
class SecondProvidePage extends StatefulWidget {
const SecondProvidePage({Key key}) : super(key: key);
@override _SecondProvideState createState() => _SecondProvideState();
}
class _SecondProvideState extends State<SecondProvidePage> {
@override Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text("第二个页面"),),
body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'这是第二个页面', style: TextStyle(fontSize: 30.0, color: Colors.red),),
Provide<MyProvide>(builder: (context, child, provide) {
return Text('阅读人数:' + '${provide.getNum}',
style: const TextStyle(fontSize: 30.0),);
},),
Provide<MyProvide>(builder: (context, child, provide) {
return IconButton(icon: Provide
.value<MyProvide>(context)
.getIsFavorite
? const Icon(Icons.favorite, size: 50)
: const Icon(Icons.favorite_border, size: 50), onPressed: () {
Provide.value<MyProvide>(context).changeFavorite(!Provide
.value<MyProvide>(context)
.getIsFavorite);
},);
},),
],),),
floatingActionButton: FloatingActionButton(onPressed: () {
Provide.value<MyProvide>(context).addNum();
}, child: const Icon(Icons.add),),);
}
}
5 Flutter Key
5.1 说明
我们平时一定接触过很多的 Widget,比如 Container、 Row、Column 等,它们在我们绘制界面的过程 中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同 的参数,它们通常在参数列表的第一个,那就是 Key。
在Flutter中, Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。
5.2 没有 Key 会发生什么现象
案例
如下面例:定义了一个StatefulWidget的Box,点击Box的时候可以改变Box里面的数字,当我们重新 对Box排序的时候Flutter就无法识别到Box的变化了, 这是什么原因呢?
图例
说明
运行后我们发现改变list Widget顺序后,Widget颜色会变化,但是每个Widget里面的文本内容并没有 变化,为什么会这样呢?当我们List重新排序后Flutter检测到了Widget的顺序变化,所以重新绘制List Widget,但是Flutter 发现List Widget 里面的元素没有变化,所以就没有改变Widget里面的内容。
把List 里面的Box的颜色改成一样,这个时候您重新对list进行排序,就很容易理解了。重新排序后虽然 执行了setState,但是代码和以前是一样的,所以Flutter不会重构List Widget里面的内容, 也就是 Flutter没法通过Box里面传入的参数来识别Box是否改变。如果要让FLutter能识别到List Widget子元素 的改变,就需要给每个Box指定一个key。
代码
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key})
: super(key: key); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [
Center(
child: Box(
color: Colors.blue,
),
),
Box(
color: Colors.blue,
),
Box(
color: Colors.red,
),
Box(
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
class Box extends StatefulWidget {
Color color;
Box({this.color});
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
),
),
);
}
}
5.3 LocalKey与GlobalKey
5.3.1 说明
在Flutter中, Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。
5.3.2 Flutter key子类
LocalKey
局部键(LocalKey): ValueKey、ObjectKey、 UniqueKey
GlobalKey
全局键(GlobalKey): GlobalKey、GlobalObjectKey
图例
ValueKey (值key)把一个值作为key , UniqueKey (唯一key)程序生成唯一的Key,当我们不知道 如何指定ValueKey的时候就可以使用UniqueKey ,ObjectKey (对象key)把一个对象实例作为key。
GlobalKey (全局key), GlobalObjectKey (全局Objec key,和ObjectKey有点类似)
5.3.3 运用key变更上面案例业务
代码
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key})
: super(key: key); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [
Box(
key: const ValueKey(1),
color: Colors.blue,
),
Box(
key: ObjectKey(Box(color: Colors.red)),
color: Colors.red,
),
Box(
key: UniqueKey(), //程序自动生成一个key
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
class Box extends StatefulWidget {
Color color;
Box({Key key, this.color}) : super(key: key);
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
),
),
);
}
}
5.3.4 GlobalKey的使用
说明
如果把LocalKey比作局部变量, GlobalKey就类似于全局变量
下面使用了LocalKey,当屏幕状态改变的时候把 Colum换成了Row , Box的状态就会丢失。
一个Widget状态的保存主要是通过判断组件的类型或者key值是否一致。 LocalKey只在当前的组件树有效,所以把Colum换成了Row的时候Widget的状态就丢失了。为了解决这个问题我们 就可以使用GlobalKey。
案例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key})
: super(key: key); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [
Box(
key: const ValueKey(1),
color: Colors.blue,
),
Box(
key: ObjectKey(Box(color: Colors.red)),
color: Colors.red,
),
Box(
key: UniqueKey(), //程序自动生成一个key
color: Colors.orange,
)
];
List<Widget> list = [
Box(
key: GlobalKey(),
color: Colors.blue,
),
Box(
key: GlobalKey(),
color: Colors.red,
),
Box(
key: GlobalKey(), //程序自动生成一个key
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: MediaQuery.of(context).orientation == Orientation.portrait
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
class Box extends StatefulWidget {
Color color;
Box({Key key, this.color}) : super(key: key);
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
),
),
);
}
}
5.3.5 GlobalKey 获取子组件
说明
globalKey.currentState 可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget可以获 取子组件的属性, _globalKey.currentContext!.findRenderObject()可以获取渲染的属性。
案例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key})
: super(key: key); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
//1、获取子组件的状态 调用子组件的属性
var state = (_globalKey.currentState as _BoxState);
setState(() {
state._count++;
}); //2、获取子组件的属性
var box = (_globalKey.currentWidget as Box);
print(box.color); //3、获取子组件渲染的属性
var renderBox =
(_globalKey.currentContext.findRenderObject() as RenderBox);
print(renderBox.size);
},
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Box(
key: _globalKey,
color: Colors.red,
),
),
);
}
}
class Box extends StatefulWidget {
final Color color;
const Box({Key key, this.color}) : super(key: key);
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
run() {
print("run");
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
),
),
);
}
}