Key & 局部渲染
详细代码参见Demo
一、补充:
17、Flutter - Flutter生命周期 & Widget 的生命周期 & Inherited数据传递
生命周期
生命周期就是处理数据
没有监听生命周期的方法就是没有生命周期方法
有状态的Stateful
didChangeDependencies 改变依赖关系
在 init之后会调用一次
然后就是在数据共享 Inherit 的时候才会调用
数据共享就是为了解决有很多层级,需要共享数据的时候,不需要在一层一层的传递,共享数据,都能访问。
状态管理有哪些
数据共享就是状态管理
状态管理也叫数据管理
element
并不是widget 都是独立渲染(这里说的是独立渲染,Widget是否被渲染是根据Element树来决定的)
特殊的widget 每个
Widget 树在变化的时候,element 树并不一定在变化
Render 的才是真正渲染的树
为什么选用 element 作为 context ?
因为 element 里面持有 widget 和 Render 所以有了Element就同时拥有了Widget 和 Render。Render 里面没有element,再有就是context 的字面意思就是上下文,所以需要拿到更全的数据用来处理。
二、Key
先看一下几个源码
StatelessWidget
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key key }) : super(key: key);
AppBar
class AppBar extends StatefulWidget implements PreferredSizeWidget {
AppBar({
Key key,
Scaffold
class Scaffold extends StatefulWidget {
const Scaffold({
Key key,
Text
class Text extends StatelessWidget {
const Text(
this.data, {
Key key,
里面都有一个可选参数 key
现在来写个demo验证一下这个key的用途
import 'package:flutter/material.dart';
import 'key_demo2.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
body: KeyDemo(),
));
}
}
key_demo.dart
import 'dart:math';
import 'package:flutter/material.dart';
class KeyDemo extends StatefulWidget {
@override
_KeyDemoState createState() => _KeyDemoState();
}
class _KeyDemoState extends State<KeyDemo> {
List<Widget> items = [
// StlItem1('aaa'),
// StlItem1('bbb'),
// StlItem1('ccc'),
// -------------------------------
// StfulItem2('aaa'),
// StfulItem2('bbb'),
// StfulItem2('ccc'),
// -------------key------------------
StfulItem2('aaa', key: ValueKey('111')),
StfulItem2('bbb', key: ValueKey('222')),
StfulItem2('ccc', key: ValueKey('333')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('KeyDemo'),
),
body: Row(mainAxisAlignment: MainAxisAlignment.center, children: items),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
items.removeAt(0);
});
},
),
);
}
}
//做一个正方形;
// ------------------ StatelessWidget 没问题 ----------------------------
class StlItem1 extends StatelessWidget {
final title;
StlItem1(this.title);
// 随机颜色 Random().nextInt(256) 随机正整数 0-255
final _color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: _color,
child: Text(title),
);
}
}
如上代码使用StatelessWidget 点击删除item 正常
// ---------------------- StatefulWidget ----------------------------
class StfulItem2 extends StatefulWidget {
final title;
// StfulItem2(this.title);
StfulItem2(this.title, {Key key}) : super(key: key);
@override
_StfulItem2State createState() => _StfulItem2State();
}
class _StfulItem2State extends State<StfulItem2> {
// 随机颜色 Random().nextInt(256) 随机正整数 0-255
// 如果颜色也写到Widget中就不会出现,删除的时候颜色和文字不对的问题
final _color = Color.fromRGBO(
Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: _color,
child: Text(widget.title),
);
}
}
使用StatefulWidget 在Widget里面写_color 看页面效果也是正常的。
但是把_color 写到State里面就出问题了。效果如下
然后加上key
StfulItem2(this.title, {Key key}) : super(key: key);
StfulItem2('aaa', key: ValueKey('111')),
StfulItem2('bbb', key: ValueKey('222')),
StfulItem2('ccc', key: ValueKey('333')),
加上key之后就正常了
在Widget里面是正常的,在State里面就不正常了。猜测应该是颜色错乱了。
看一下源码为什么 StatefulWidget
abstract class StatefulWidget extends Widget {
进入到Widget
abstract class Widget extends DiagnosticableTree {
这里面有个方法
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
runtimeType 真实类型
有删除排序等一定要加入key
Flutter使用的时候自己的引擎,将其引擎加入到项目中。这样做有几个好处就是
1、性能更快
2、在iOS和安卓中用都都是自己的不存在过大的差异
3、iOS和安卓在更新的时候对其他影响不大
2.2、key分析
Key 本身是一个抽象类
1、LocalKey
用作diff算法的核心所在!用作Element 和 Widget 进行比较
Valuekey
数字 或 字符等任意类型。以一个数据作为key
ObjectKey
上面的代码中的Valuekey 可以换成ObjectKey,包装一个对象 例如: ObjectKey(Text(‘123’))
UniqueKey 可以保证key的唯一性。返回的是一个Hash,一个随机值,保证唯一。用法:UniqueKey();
一旦使用,就不存在Element的复用了,一旦刷新所有的Widget都会重新渲染。这个用的不多一般用来强制更新
LocalKey 也是一个抽象类
查看Valuekey 源码
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
2、Globakey
可以获取到对应的widget 的state 对象
key_demo2.dart
import 'package:flutter/material.dart';
class GlobalKeyDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GlobalKeyDemo'),
),
body: ChildPage(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
},
),
);
}
}
class ChildPage extends StatefulWidget {
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[Text(count.toString()), Text(data)],
),
);
}
}
StatelessWidget 添加button,点击去修改StatefulWidget 的话,按我们原来的学习应该是添加button改变的地方必须使用StatefulWidget,被调用刷新的使用StatelessWidget。
但是尽量在末端使用Stateful,也就是树的“叶子”处。因为Stateful变化的时候后面的左右都会被重新渲染
方法一:可以在StatefulWidget 里面加个回调,在StatelessWidget去调用的时候将值传进去
方法二:
GlobalKey 可以获取到对应的Widget 的 State对象
import 'package:flutter/material.dart';
class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GlobalKeyDemo'),
),
body: ChildPage(
key: _globalKey,
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_globalKey.currentState.count++;
_globalKey.currentState.data =
'old' + _globalKey.currentState.count.toString();
// 局部渲染和跟新
_globalKey.currentState.setState(() {});
},
),
);
}
}
class ChildPage extends StatefulWidget {
ChildPage({Key key}) : super(key: key);
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[Text(count.toString()), Text(data)],
),
);
}
}
这样就解决了。
最后总结一下:
1、LocalKey 用来获取Widget
2、全局Key
全局Key 用来获取对应的State、Widget、Element
获取的目的是减少渲染复制引擎。比较常见的用来做局部渲染