Flutter中有四种widget
StatelessWidget
StatefullWidget
RenderObjectWidget
InheritedWidget
其中StatelessWidget和StatefulWidget是最常见到的,从状态管理角度的分类。RenderObjectWidget是所有需要渲染的Widget的基类。
至于最后一个InheritedWidget,许多初学者不一定了解,但是在一些稍微复杂的项目中是必须要用到的,所以本文介绍一下InheritedWidget的用法
InheritedWidget
To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state.
通常情况下,子widget无法单独感知父widget的变化,当父state变化时,通过其build重建所有子widget;
InheritedWidget可以避免这种全局创建,实现局部的子widget更新:
子widget通过BuildContext.inheritFromWidgetOfExactType从buildContext中获取并监听指定类型的父InheritedWidget,并跟随其重建而rebuild
如上图,点击C按钮,State变化后,A的Text可以单独刷新,B不受到影响
代码演示
接下来通过代码对比一下使用或不使用InheritedWidget的区别:
点击+,后上面的0变化,中间的文字部分不变化。
传统实现
点击按钮state变化后,widgetA、B、C都会rebuild
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(_counter),
WidgetB(),
WidgetC(_incrementCounter),
],
),
);
}
}
class WidgetA extends StatelessWidget {
final int counter;
WidgetA(this.counter);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
'${counter}',
style: Theme.of(context).textTheme.display1,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
class WidgetC extends StatelessWidget {
final void Function() incrementCounter;
WidgetC(this.incrementCounter);
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
incrementCounter();
},
child: Icon(Icons.add),
);
}
}
使用AndroidStudio的Flutter Performance可以看到widgetA、B、C都参与了rebuild
使用InheritedWidget实现
import 'package:flutter/material.dart';
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);
}
}
class _MyInheritedWidget extends InheritedWidget {
final HomePageState data;
_MyInheritedWidget({ Key key, Widget child, this.data }) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key key, @required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>().data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>().data;
// or
// return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $counter');
});
}
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);
return Center(
child: Text(
'${state?.counter}',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, rebuild: false);
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
注意:本demo 2022.3.5亲自验证通过,使用的flutter sdk版本位1.22.4
下面是flutter sdk 最新版本demo,支持null-safety
import 'package:flutter/material.dart';
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);
}
}
class _MyInheritedWidget extends InheritedWidget {
final HomePageState data;
_MyInheritedWidget({ Key? key, required Widget child, required this.data }) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key? key, required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState? of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>()?.data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>()?.data;
//or
//return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $counter');
});
}
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState? state = HomePage.of(context);
return Center(
child: Text(
'${state?.counter}',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState? state = HomePage.of(context, rebuild: false);
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
可以看到state变化时,widgetB、C都没有rebuild
关键代码说明
针对InheritedWidget版本中的关键类进行说明
WidgetA、WidgetC
传统版本中WidgetA、C通过构造函数传入父级的state以及回调
InheritedWidget版本中,可以通过如下静态方法获取
final HomePageState state = HomePage.of(context); // WidgetA
final HomePageState state = HomePage.of(context, rebuild: false); // WidgetC
WidgetC是一个Button需要通过state获取回调方法,但不需要跟随state变化而刷新,所以rebuild指定false
接下来详细看一下获取state的静态方法 HomePage.of
HomePage
static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>().data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>().data;
// or
// return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
HomePage.of用来通过buildContext,找到最近的_MyInheritedWidget。然后就可以同_MyInheritedWidget获取其持有的state。
获取上级Widget的几个关键方法如下:
method | description |
---|---|
inheritFromWidgetOfExactType | 获取最近的给定类型的上级Widget,该widget必须是InheritedWidget的子类,并向上级widget注册传入的context,当上级widget改变时,这个context持有的widget会rebuild以便从该widget获得新的值。这就是child向InheritedWidget注册的方法。 |
ancestorWidgetOfExactType | 仅仅用来获取最近的给定类型的上级Widget,不会因为上级Widget的改变而rebuild |
ancestorInheritedElementForWidgetOfExactType | 功能与inheritFromWidgetOfExactType一样,但是只会寻找InheritedWidget的子类,所以可以以O(1)的复杂度查找上级Widget |
上面的方法已经过时,flutter新版本的应该是:
method | description |
---|---|
dependOnInheritedWidgetOfExactType | 获取最近的给定类型的上级Widget,该widget必须是InheritedWidget的子类,并向上级widget注册传入的context,当上级widget改变时,这个context持有的widget会rebuild以便从该widget获得新的值。这就是child向InheritedWidget注册的方法。 |
findAncestorWidgetOfExactType | 仅仅用来获取最近的给定类型的上级Widget,不会因为上级Widget的改变而rebuild |
getElementForInheritedWidgetOfExactType | 功能与findAncestorWidgetOfExactType一样,但是只会寻找InheritedWidget的子类,所以可以以O(1)的复杂度查找上级Widget |
因此,widgetA随着父widget的变化而rebuild,widgetB并没有rebuild
class _MyInheritedWidget extends InheritedWidget {
_MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final HomePageState data;
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
继承自InheritedWidget,所以子Widget可以通过dependOnInheritedWidgetOfExactType获取。
updateShouldNotify控制是否需要子widget感受其变化,如果返回true,则通过dependOnInheritedWidgetOfExactType注册的子widget跟随其变化rebuild
子widget最终目的是要获取共享的父级state,所以这里通过data属性持有了state。
那再来看一下这个HomePageState
HomePageState
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
此处_MyInheritedWidget的使用是关键。
传统写法中,build中直接创建widgetA、B、C并返回,因此每当state变化时,会重新创建子widget并rebuild;
InheritedWidget版本中,HomePage保持父widget(TopPage)的children,当state变化时widgetA、B、C不会重建,而是重新传入给_MyInheritedWidget,重建的只有_MyInheritedWidget
TopPage
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
・・・
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(), // 子widget的创建移动到这里
WidgetB(),
WidgetC(),
],
),
・・・
}
}
根据上文的说明,为了避免子widget的反复创建和rebuild,将widgetA、B、C的实例化移动到这里
InheritedModel
上面的例子中我们通过自定义了rebuild参数来指定子Widget是否参与rebuild,实际上也可以使用InheritedModel完成此需求
InheritedModel继承自InheritedWidget,可以通过字符串key(aspect)来指定特定子widget进行rebuild。
简单看一下InheritedModel版本与InheritedWidget版本在实现上的不同
@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
}
}
使用 InheritedModel.inheritFrom获取widget
class _MyInheritedWidget extends InheritedModel {
@override
bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) {
return aspects.contains('A'); // 当aspect包晗“A”时,通知其rebuild
}
}
继承InheritedModel,重写updateShouldNotifyDependent
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'A'); // 注册aspect为“A“
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'C'); // 注册aspect为“C”
如上,因为注册的key(aspect)不同,只有widgetA会受到rebuild的通知
完整代码如下:
import 'package:flutter/material.dart';
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);
}
}
class _MyInheritedWidget extends InheritedModel {
final HomePageState data;
_MyInheritedWidget({ Key key, Widget child, this.data }) : super(key: key, child: child);
@override
bool updateShouldNotifyDependent(covariant InheritedModel oldWidget, Set dependencies) {
return dependencies.contains('A'); // 当dependencies包晗“A”时,通知其rebuild
}
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key key, @required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $counter');
});
}
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'A');
return Center(
child: Text(
'${state?.counter}',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'C');
print('WidgetC counter ${state.counter}');
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
更局部的刷新
如果widgetA是下面这样,我们希望能进一步控制其子widget的局部刷新
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);
return Column(
children: <Widget>[
Center(
child: Text(
'${state.counter}',
style: Theme.of(context).textTheme.display1,
),
),
Text("AAAAA"), // 此处不需rebuild
],
);
}
}
如果彻底理解了BuildContext和InheritedWidget的注册机制,是可以很容易实现的:
return Column(
children: <Widget>[
Center(
child: Builder(builder: (context){
final HomePageState state = HomePage.of(context);
return Text(
'${state.counter}',
style: Theme.of(context).textTheme.display1,
);
}),
),
Text("AAAAA"),
],
);
通过Builder来创建一个匿名类widget,然后将HomePage.of移到其内部。此时InheritedWidget中注册的context不再是widgetA而是这个匿名类widget,因此可以实现widgetA的局部刷新
不使用InheritedWidget
我想通过上文的介绍大家应该能够想到,如果子widget仅仅想访问父级state(不通过构造函数传参的方式),但没有监听其变化的需要,可以不使用InheritedWidget:
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
HomePageState state; // 持有state供子类获取
@override
HomePageState createState() {
state = HomePageState();
return state;
}
}
class HomePageState extends State<HomePage> {
int counter = 0; // 去掉private
void incrementCounter() { // 去掉private
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage); // 获取state
final HomePageState state = widget?.state;
return Center(
child: Text(
'${state == null ? 0 : state.counter}',
style: Theme.of(context).textTheme.display1,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage);
final HomePageState state = widget?.state;
return RaisedButton(
onPressed: () {
state?.incrementCounter();
},
child: Icon(Icons.add),
);
}
}
通过ancestorWidgetOfExactType寻找指定类型的widget,然后获取其state使用,当然这个遍历是O(n)的,性能比InheritedWidget版本要差
最后
Flutter中很多组件都是基于InheritedWidget实现的,例如Scoped Model、BLoC(Business Logic of component)等,想要掌握这些高级特性的使用先从了解InheritedWidget开始吧
延伸:
Flutter使用InheritedModel实现局部刷新
定义的数据模型为
import 'package:flutter/material.dart';
import 'user_type.dart';
class UserInheritedModel extends InheritedModel<UserType> {
final int age;
final int weight;
const UserInheritedModel(
{required this.age, required this.weight, required Widget child})
: super(child: child);
static UserInheritedModel? of(BuildContext context,
{required UserType aspect}) {
return InheritedModel.inheritFrom<UserInheritedModel>(context,
aspect: aspect);
}
@override
bool updateShouldNotify(UserInheritedModel old) {
return age != old.age || weight != old.weight;
}
@override
bool updateShouldNotifyDependent(
UserInheritedModel old, Set<UserType> aspects) {
return (aspects.contains(UserType.age) && age != old.age) ||
(aspects.contains(UserType.height) && weight != old.weight);
}
}
要局部刷新的页面为
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
child: const AgePage(
ideaType: UserType.age,
),
onTap: () {
setState(() {
_age += 1;
});
},
),
Divider(),
InkWell(
child: const WeightPage(
ideaType: UserType.height,
),
onTap: () {
setState(() {
_weight += 1;
});
},
),
],
),
包含的页面为
class AgePage extends StatelessWidget {
final UserType ideaType;
const AgePage({Key? key, required this.ideaType}) : super(key: key);
@override
Widget build(BuildContext context) {
final UserInheritedModel? _ideasTypeIdea =
UserInheritedModel.of(context, aspect: ideaType);
return Text(
'${_ideasTypeIdea!.age}\n${Random.secure().nextDouble()}',
);
}
}
另一个页面与上面的类似
What is InheritedWidget and How it works in a Flutter??
上面的文章介绍了InheritedWidget的实现原理,下面把改动过的demo贴出来,供参考。
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
final Widget child;
const MyStatefulWidget({Key key, @required this.child}) : super(key: key);
static MyStatefulWidgetState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data;
}
@override
State<StatefulWidget> createState() {
return MyStatefulWidgetState();
}
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counterValue = 0;
int get counterValue => _counterValue;
void addCounterBy() {
setState(() {
_counterValue += 1;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedWidget(
child: widget.child,
data: this,
);
}
}
class MyInheritedWidget extends InheritedWidget {
final MyStatefulWidgetState data;
MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
class MyContainer extends StatelessWidget {
final Widget child;
MyContainer({
Key key,
@required this.child,
}) : super(key: key);
void onPressed(BuildContext context) {
MyStatefulWidget.of(context).addCounterBy();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
child: RaisedButton(
color: Colors.red,
onPressed: (){
onPressed(context);
},
child: child,
),
),
);
}
}
class DummyContainer extends StatelessWidget {
final Widget child;
const DummyContainer({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return child;
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
"Counter",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.',
style: TextStyle(
fontSize: 14,
color: Colors.white,
),);
}
}
class WidgetC extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return CounterValueState();
}
}
class CounterValueState extends State<WidgetC> {
int counterValue;
double fontSize;
@override
void didChangeDependencies() {
super.didChangeDependencies();
MyStatefulWidgetState data = MyStatefulWidget.of(context);
counterValue = data.counterValue;
fontSize = 50.0 + counterValue;
}
@override
Widget build(BuildContext context) {
return Text(
"$counterValue",
style: TextStyle(
fontSize: fontSize,
color: Colors.white,
),
);
}
}