Flutter中有兩個(gè)常用的狀態(tài)Widget分為StatefulWidget和StatelessWidget,分別為動(dòng)態(tài)視圖和靜態(tài)視圖,視圖的更新需要調(diào)用StatefulWidget的setState方法,這會遍歷調(diào)用子Widget的build方法。如果一個(gè)頁面內(nèi)容比較復(fù)雜時(shí)锁右,會包含多個(gè)widget失受,如果直接調(diào)用setState讶泰,會遍歷所有子Widget的build咏瑟,這樣會造成很多不必要的開銷,所以非常有必要了解Flutter中局部刷新的方式:
通過GlobalKey局部刷新
globalkey唯一定義了某個(gè)element痪署,它使你能夠訪問與element相關(guān)聯(lián)的其他對象码泞,例如buildContext、state等狼犯。應(yīng)用場景:跨widget訪問狀態(tài)余寥。
例如:可以通過key.currentState拿到它的狀態(tài)對象,然后就可以調(diào)用其中的onPressed方法悯森。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
GlobalKey<_TextWidgetState> textKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
TextWidget(textKey),// 需要更新的Text
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
_counter ++;// 這里我們只給他值變動(dòng)宋舷,狀態(tài)刷新交給下面的key事件
textKey.currentState.onPressed(_counter);//這個(gè)counter的值已經(jīng)改變了,但是沒有重繪所以我們看到的知識我們定義的初始值
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class TextWidget extends StatefulWidget {
final Key key;
const TextWidget(this.key);
@override
_TextWidgetState createState() => _TextWidgetState();
}
class _TextWidgetState extends State<TextWidget> {
String _text = "0";
@override
Widget build(BuildContext context) {
return Center(child: Text(_text, style: TextStyle(fontSize: 20),),);
}
void onPressed(int count) {
setState(() {
_text = count.toString();
});
}
}
ValueNotifier和ValueListenableBuilder
Flutter框架內(nèi)部提供了一個(gè)非常小巧精致的組件瓢姻,專門用于局部組件的刷新祝蝠。適用于值改動(dòng)的刷新。
class ValueNotifierTestPage extends StatefulWidget {
@override
_ValueNotifierTestPageState createState() => _ValueNotifierTestPageState();
}
class _ValueNotifierTestPageState extends State<ValueNotifierTestPage> {
// 定義ValueNotifier<int> 對象 _counter
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
void _incrementCounter(){
_counter.value += 1;
}
@override
Widget build(BuildContext context) {
return Center(
child: Scaffold(
appBar: AppBar(
title: Text('ValueNotifierTestPage'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
WidgetA(),
Text( 'You have pushed the button this many times:'),
ValueListenableBuilder<int>(
builder: _buildWithValue,
valueListenable: _counter,
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
),
);
}
Widget _buildWithValue(BuildContext context, int value, Widget child) {
return Text(
'$value',
style: Theme.of(context).textTheme.headline4,
);
}
@override
void dispose() {
_counter.dispose();
super.dispose();
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.');
}
}
實(shí)現(xiàn)原理:在 initState 中對傳入的可監(jiān)聽對象進(jìn)行監(jiān)聽幻碱,執(zhí)行 _valueChanged 方法绎狭,_valueChanged 中進(jìn)行了 setState 來觸發(fā)當(dāng)前狀態(tài)的刷新。觸發(fā) build 方法褥傍,從而觸發(fā) widget.builder 回調(diào)儡嘶,這樣就實(shí)現(xiàn)了局部刷新』蟹纾可以看到這里回調(diào)的 child 是組件傳入的 child蹦狂,所以直接使用,這就是對 child 的優(yōu)化的根源朋贬。
可以看到 ValueListenableBuilder 實(shí)現(xiàn)局部刷新的本質(zhì)鸥咖,也是進(jìn)行組件的抽離,讓組件狀態(tài)的改變框定在狀態(tài)內(nèi)部兄世,并通過 builder 回調(diào)控制局部刷新啼辣,暴露給用戶使用。
StatefulBuilder局部刷新
通過這個(gè)可以創(chuàng)建一個(gè)支持局部刷新的widget樹御滩,比如你可以在StatelessWidget里面刷新某個(gè)布局鸥拧,但是不需要改變成StatefulWidget;也可以在StatefulWidget中使用做部分刷新而不需要刷新整個(gè)頁面削解,這個(gè)刷新是不會調(diào)用Widget build(BuildContext context)刷新整個(gè)布局樹的富弦。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
StateSetter _reloadTextSetter;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StatefulBuilder(builder: (BuildContext context, StateSetter stateSetter){
_reloadTextSetter = stateSetter;
return Text(_counter.toString());
})// 需要更新的Text
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
_counter ++;
_reloadTextSetter((){
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
FutureBuilder & StreamBuilder
異步UI更新:
很多時(shí)候我們會依賴一些異步數(shù)據(jù)來動(dòng)態(tài)更新UI,比如在打開一個(gè)頁面時(shí)我們需要先從互聯(lián)網(wǎng)上獲取數(shù)據(jù)氛驮,在獲取數(shù)據(jù)的過程中顯示一個(gè)加載框腕柜,等獲取到數(shù)據(jù)時(shí)我們再渲染頁面;又比如我們想展示Stream(比如文件流、互聯(lián)網(wǎng)數(shù)據(jù)接收流)的進(jìn)度盏缤。當(dāng)然StatefulWidget我們完全可以實(shí)現(xiàn)以上功能砰蠢。但由于在實(shí)際開發(fā)中依賴異步數(shù)據(jù)更新UI的這種場景非常常見,并且當(dāng)StatefulWidget中控件樹較大時(shí)唉铜,更新一個(gè)屬性導(dǎo)致整個(gè)樹重建台舱,消耗性能,因此Flutter專門提供了FutureBuilder和SteamBuilder兩個(gè)組件來快速實(shí)現(xiàn)這種功能潭流。
class _MyHomePageState extends State<MyHomePage> {
Future<String> mockNetworkData() async {
return Future.delayed(Duration(seconds: 2), () => "我是從互聯(lián)網(wǎng)上獲取的數(shù)據(jù)");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
FutureBuilder(
future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.connectionState == ConnectionState.done){
if(snapshot.hasError){
// 請求失敗竞惋,顯示錯(cuò)誤
return Text("Error: ${snapshot.error}");
}else {
// 請求成功,顯示數(shù)據(jù)
return Text("Contents: ${snapshot.data}");
}
}else {
return CircularProgressIndicator();
}
}),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class _MyHomePageState extends State<MyHomePage> {
Stream<int> counter(){
return Stream.periodic(Duration(seconds: 1), (i){
return i;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: counter(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot){
if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
}
switch (snapshot.connectionState){
case ConnectionState.none:
return Text("沒有Stream");
case ConnectionState.waiting:
return Text("等待數(shù)據(jù)灰嫉、拆宛、、");
case ConnectionState.active:
return Text("active: ${snapshot.data}");
case ConnectionState.done:
return Text("Stream已關(guān)閉");
}
return null;
}),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
InheritedWidget
通常情況下讼撒,子Widget無法單獨(dú)感知父Widget的變化胰挑,當(dāng)父state變化時(shí),通過其build重建所有子widget椿肩;
InheriteddWidget可以避免這種全局創(chuàng)建瞻颂,實(shí)現(xiàn)局部子Widget更新。InheritedWidget提供了一種在Widget樹中從上到下傳遞郑象、共享數(shù)據(jù)的方式贡这。Flutter SDK正是通過InheritedWidget來共享應(yīng)用主題和Locale等信息。
InheritedWidgetData
class InheritedWidgetData<T> extends InheritedWidget {
InheritedWidgetData({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final T data;
@override
bool updateShouldNotify(InheritedWidgetData<T> oldWidget) {
return true;
}
}
TestData
class TestData extends StatefulWidget {
TestData({
Key key,
this.child,
}) : super(key: key);
final Widget child;
@override
_TestDataState createState() => _TestDataState();
static _TestDataState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<InheritedWidgetData<_TestDataState>>().data;
}
return context.findAncestorWidgetOfExactType<InheritedWidgetData<_TestDataState>>().data;
}
}
class _TestDataState extends State<TestData> {
int counter = 0;
void _incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return InheritedWidgetData(
data: this,
child: widget.child,
);
}
}
InheritedTest1Page
import 'package:flutter/material.dart';
class InheritedTest1Page extends StatefulWidget {
@override
_InheritedTest1PageState createState() => _InheritedTest1PageState();
}
class _InheritedTest1PageState extends State<InheritedTest1Page> {
@override
Widget build(BuildContext context) {
return Center(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidgetTest'),
),
body: TestData(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: Text('我們常使用的\nTheme.of(context).textTheme\nMediaQuery.of(context).size等\n就是通過InheritedWidget實(shí)現(xiàn)的',
style: Theme.of(context).textTheme.title,),
),
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _TestDataState state = TestData.of(context);
return Center(
child: Text(
'${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 _TestDataState state = TestData.of(context, rebuild: false);
return RaisedButton(
onPressed: () {
state._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
ChangeNotifierProvider(Provider)
provider是Google I/O 2019大會上宣布的現(xiàn)在官方推薦的管理方式厂榛,而ChangeNotifierProvider可以說是Provider的一種:
yaml文件需要引入provider: ^3.1.0
頂層嵌套ChangeNotifierProvider
void main(){
runApp(ChangeNotifierProvider(builder: (context) => DataInfo(), child: MyApp(),));
}
創(chuàng)建共享數(shù)據(jù)類DataInfo:
數(shù)據(jù)類需要with ChangeNotifier 以使用 notifyListeners()函數(shù)通知監(jiān)聽者更新界面盖矫。
class DataInfo with ChangeNotifier {
int _count = 0;
get count => _count;
addCount(){
_count ++;
notifyListeners();
}
subCount(){
_count --;
notifyListeners();
}
}
使用Provider.of(context)獲取DataInfo
@override
Widget build(BuildContext context) {
var dataInfo = Provider.of<DataInfo>(context);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
heroTag: "add",
child: Icon(Icons.add),
onPressed: (){
dataInfo.addCount();
}
),
Text(
'${dataInfo.count}',
),
FloatingActionButton(
heroTag: "sub",
child: Icon(Icons.add),
onPressed: (){
dataInfo.subCount();
})
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).push(CupertinoPageRoute(builder: (con){
return NextPage();
}));
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
nextPage:
使用Consumer包住需要使用共享數(shù)據(jù)的Widget
@override
Widget build(BuildContext context) {
return Consumer<DataInfo>(
builder: (context, dataInfo, _){
return Scaffold(
appBar: AppBar(
title: Text("next"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
heroTag: "add",
child: Icon(Icons.add),
onPressed: (){
dataInfo.addCount();
}
),
Text(
'${dataInfo.count}',
),
FloatingActionButton(
heroTag: "sub",
child: Icon(Icons.add),
onPressed: (){
dataInfo.subCount();
})
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).pop();
},
tooltip: 'Increment',
child: Icon(Icons.settings_input_composite),
), // This trailing comma makes auto-formatting nicer for build methods.
);
},
);
}
RepaintBoundary
RepaintBoundary就是重繪邊界,用于重繪時(shí)獨(dú)立于父視圖击奶。頁面需要更新的頁面結(jié)構(gòu)可以用 RepaintBoundary組件嵌套,flutter 會將包含的組件獨(dú)立出一層"畫布"辈双,去繪制。官方很多組件 外層也包了層 RepaintBoundary 標(biāo)簽柜砾。如果你的自定義view比較復(fù)雜湃望,應(yīng)該盡可能的避免重繪。
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) { // 為true時(shí)痰驱,直接合成視圖证芭,避免重繪
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
if (owner != null)
owner.requestVisualUpdate();
}
}
class RepainBoundaryPage extends StatefulWidget {
@override
_RepainBoundaryPageState createState() => _RepainBoundaryPageState();
}
class _RepainBoundaryPageState extends State<RepainBoundaryPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Scaffold(
appBar: AppBar(
title: Text('RepainBoundaryPage'),
),
body: Column(
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
);
}
}
class WidgetC extends StatefulWidget {
@override
_WidgetCState createState() => _WidgetCState();
}
class _WidgetCState extends State<WidgetC> {
int counter = 0;
@override
Widget build(BuildContext context) {
return RepaintBoundary(child: Column(
children: [
Text('~~~${counter}'),
RaisedButton(
onPressed: () {
setState(() {
counter++;
});
},
child: Icon(Icons.add),
)
],
),);
}
}
以上總結(jié)了幾種Flutter的局部刷新的方式,可根據(jù)實(shí)際需要使用不同的方式担映,最適合的才是最好的废士。