总结一共四种方案可以避免页面刷新,仅仅使用局部刷新或按需刷新
- PageView/TabBarView这种切换的状态保存
- 局部小UI使用创建 child Statefull class,这个比较简单,暂且不表
- 使用Provider库解决
- 使用Futurebuilder的场景下如何避免
以下场景会触发build 方法调用
- initState
- didUpdateWidget
- 调用setState
- 键盘弹出
- 屏幕方向变化
- 父组件更新子组件也跟着更新
- 路由变化
1. PageView/TabBarView等控件保存状态的官方推荐问题解决方案
//关键是继承 AutomaticKeepAliveClientMixin
class _ExamplePageState extends State<ExamplePage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
........
}
其他offstage等方法都太复杂了。
AutomaticKeepAliveClientMixin还有一个大用处,就是如果这个页面监听了来自其他页面的EventBus事件,如果没有这个,flutter会销毁掉这个非激活页面,从而导致监听失效。
这个方法虽然短平快,但是也有问题,想要控制这个页面的keepAlive状态,就不能用了,这时候可以把with混入的方法自己写进来。比如
class _ExamplePageState extends State<ExamplePage> with TickerProviderStateMixin{
KeepAliveHandle _keepAliveHandle;
//来自 AutomaticKeepAliveClientMixin
void _ensureKeepAlive() {
assert(_keepAliveHandle == null);
_keepAliveHandle = KeepAliveHandle();
KeepAliveNotification(_keepAliveHandle).dispatch(context);
}
//来自 AutomaticKeepAliveClientMixin
void _releaseKeepAlive() {
_keepAliveHandle.release();
_keepAliveHandle = null;
}
@override
void initState() {
//手动做处理
if (_keepAliveHandle == null) _ensureKeepAlive();
_navToListSubscription = eventBus.on<NavToList>().listen((event) {
if (_keepAliveHandle != null) _releaseKeepAlive();
});
super.initState();
}
........}
这样就能灵活控制了。
2.动态改变(删除)列表里的元素,避免setState刷新整个页面的解决方案
import 'package:flutter/material.dart';
class Alphabet{
List alphabet = ['a','b','c','d','e','f','g','h','i','j','k'];
}
class AlphabetValueNotifier extends ValueNotifier<Alphabet>{
AlphabetValueNotifier(Alphabet counter):super(counter);
removeAt(index){
value.alphabet.removeAt(index);
notifyListeners();
}
}
AlphabetValueNotifier _alphabetValueNotifier = AlphabetValueNotifier(Alphabet());
class RemoveInListExamplePage extends StatefulWidget{
const RemoveInListExamplePage({Key key}):super(key:key);
@override
_RemoveInListExamplePageState createState() => _RemoveInListExamplePageState();
}
class _RemoveInListExamplePageState extends State<RemoveInListExamplePage> {
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _onPressed,
splashColor: Colors.red,
child: Icon(Icons.slow_motion_video),
),
body: ValueListenableBuilder<Alphabet>(
valueListenable: _alphabetValueNotifier,
builder: (_, Alphabet value, __) => ListView(
controller: _scrollController,
children: List.generate(
value.alphabet.length,
(index) => Container(
height: 200.0,
color: Colors.primaries[index % Colors.primaries.length],
child: ListTile(
title: Text('Index: ${value.alphabet[index]}'),
trailing: FlatButton(
child: Text("删除"),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
onPressed: (){
_alphabetValueNotifier.removeAt(index);
},
),
),
),
),
),
),
);
}
}
3.正确使用 FutureBuilder
比如A页面用了FutureBuilder,push进去B页面,再退出B页面,在B进出的时候A页面都会重复build,这都是我们不需要的build,比如会导致页面回到最顶端。
错误示范,返回这个页面会导致build
class _VideoPageState extends State<VideoPage>{
@override
void initState() {
print("视频列表被初始化了");
super.initState();
}
@override
Widget build(BuildContext context) {
print("video build called!");
return FutureBuilder(
future: getCategoryData(),
builder: buildBody,
);
}
}
正确示范
class _VideoPageState extends State<VideoPage>{
Future category ; // 特别需要独立出来
@override
void initState() {
print("视频列表被初始化了");
super.initState();
category = getCategoryData() ;
}
@override
Widget build(BuildContext context) {
print("video build called!");
return FutureBuilder(
future: category,
builder: buildBody,
);
}
}
目前的项目暂且遇到这三种,以后如果有新的发现再做补充