1.概述
场景1:一个界面是由众多组件拼组而成。经常需要将一个组件进行封装,但此时有一个问题,如何让多个组件去共享一些值。一个最直接的方法就是通过构造函数将变量和函数一层层向下传递。如果嵌套的很深,那简直就是噩梦,如果子组件需要更新父组件的数据或者状态,再加个回调?额。。。这个时候我们可以使用Flutter提供的InheritedWidget。
- 特性:
InheritedWidget是Flutter中的一个非常重要的功能组件,它能够提供数据在widget树中从上到下进行传递。保证数据在不同子widget中进行共享。
show code ~~
import 'package:flutter/material.dart';
///数据共享
class SecondDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TestPage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
);
}
}
// ignore: must_be_immutable
class InheritedProvide extends InheritedWidget {///数据共享
InheritedProvide({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final TestPageState data;
@override
bool updateShouldNotify(InheritedProvide oldWidget) {
return true;
}
}
class TestPage extends StatefulWidget {
TestPage({
Key key,
this.child,
}) : super(key: key);
final Widget child;
@override
TestPageState createState() => TestPageState();
static TestPageState of(BuildContext context) {
// ignore: deprecated_member_use
return (context.inheritFromWidgetOfExactType(InheritedProvide) as InheritedProvide)?.data;
}
}
class TestPageState extends State<TestPage> {
int counter = 0;
void incrementPageCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return InheritedProvide(
data: this,
child: widget.child,
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context);
print('----------WidgetA build------');
return Center(
child: Text('点击的次数',),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context,);
print('----------WidgetB build------');
return new Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: new Text('${state.counter}', style: Theme.of(context).textTheme.display1,),
);
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context);
print('----------WidgetC build------');
return RaisedButton(
onPressed: () {
state.incrementPageCounter();
},
child: Icon(Icons.add),
);
}
}
问题:在上面的demo中,存在一个性能问题
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context);
print('----------WidgetC build------');
return RaisedButton(
onPressed: () {
state.incrementPageCounter();
},
child: Icon(Icons.add),
);
}
}
这个WidgetC不应该build,因为调用了final TestPageState state = TestPage.of(context);这句代码,也就是说依赖了Widget树上面的InheritedWidget(即InheritedProvider )Widget,所以当点击完按钮后,数据发生变化,会通知TestPage, 而TestPage则会重新构建子树,所以InheritedProvider将会更新,此时依赖它的子孙Widget就会被重新构建。那么如何避免WidgetC不Build呢?
- 解决方案:
既然WidgetC重新被build是因为WidgetC和InheritedWidget建立了依赖关系,那么我们只要打破或解除这种依赖关系就可以了。如何打破呢?需要用到inheritFromWidgetOfExactType和ancestorWidgetOfExactType这两个方法。
代码如下:
import 'package:flutter/material.dart';
//局部刷新
class ThirdDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TestPage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
);
}
}
// ignore: must_be_immutable
class InheritedProvider extends InheritedWidget {///数据共享
InheritedProvider({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final TestPageState data;
@override
bool updateShouldNotify(InheritedProvider oldWidget) {
return true;
}
}
class TestPage extends StatefulWidget {
TestPage({
Key key,
this.child,
}) : super(key: key);
final Widget child;
@override
TestPageState createState() => TestPageState();
static TestPageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
// ignore: deprecated_member_use
return (context.inheritFromWidgetOfExactType(InheritedProvider) as InheritedProvider)?.data;
}
// ignore: deprecated_member_use
return (context.ancestorWidgetOfExactType(InheritedProvider) as InheritedProvider)?.data;
}
}
class TestPageState extends State<TestPage> {
int counter = 0;
void incrementPageCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return InheritedProvider(
data: this,
child: widget.child,
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context);
print('----------WidgetA build------');
return Center(
child: Text('点击的次数',),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context,);
print('----------WidgetB build------');
return new Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: new Text('${state.counter}', style: Theme.of(context).textTheme.display1,),
);
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TestPageState state = TestPage.of(context, rebuild: false);
print('----------WidgetC build------');
return RaisedButton(
onPressed: () {
state.incrementPageCounter();
},
child: Icon(Icons.add),
);
}
}
再点击会发现“----------WidgetC build------”这句话不打印了。说明WidgetC不build了。
- 总结:调用inheritFromWidgetOfExactType() 和 ancestorWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会,所以在调用inheritFromWidgetOfExactType()时,InheritedWidget和依赖它的子孙组件关系便完成了注册,之后当InheritedWidget发生变化时,就会更新依赖它的子孙组件,也就是会调这些子孙组件的build()方法。而当调用的是 ancestorWidgetOfExactType()时,由于没有注册依赖关系,所以之后当InheritedWidget发生变化时,就不会更新相应的子孙Widget。
2. ValueListenableBuilder
InheritedWidget 提供一种在 widget 树中从上到下共享数据的方式,但是也有很多场景数据流向并非从上到下,比如从下到上或者横向等。为了解决这个问题,Flutter 提供了一个 ValueListenableBuilder 组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行其 builder。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
///直接调用SetState
class FourthDemo extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _FourthDemoState();
}
}
class _FourthDemoState extends State<FourthDemo>{
int counter = 0;
void incrementCounter() {
// setState(() {
counterNotifier.value = counter++;
// });
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("Dependencies change");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('FirstDemo'),),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
WidgetA(),
WidgetB(counter),
],
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('----------WidgetA build------');
return Center(
child: Text('You have pushed the button this many times:',),
);
}
}
ValueNotifier<int> counterNotifier = ValueNotifier<int>(0);
class WidgetB extends StatelessWidget {
final int counter;
WidgetB(this.counter);
@override
Widget build(BuildContext context) {
print('----------WidgetB build------');
return ValueListenableBuilder<int>(
valueListenable: counterNotifier,
builder: (BuildContext context,int counter,Widget child){
print('----------ValueListenableBuilder build------');
return Text('$counter', style: Theme.of(context).textTheme.display1,);
});
}
}
- 总结
- 和数据流向无关,可以实现任意流向的数据共享。
- 实践中,ValueListenableBuilder 的拆分粒度应该尽可能细,可以提高性能。