【Flutter】总结如何避免执行不想要的build

2,084 阅读2分钟

总结一共四种方案可以避免页面刷新,仅仅使用局部刷新或按需刷新

  1. PageView/TabBarView这种切换的状态保存
  2. 局部小UI使用创建 child Statefull class,这个比较简单,暂且不表
  3. 使用Provider库解决
  4. 使用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,
    );
  }
}

目前的项目暂且遇到这三种,以后如果有新的发现再做补充