【Flutter性能优化】几个优化技巧,减少rebuild的影响

  1. 将频繁更新的组件尽可能放在叶子节点
  2. 尽量减少由build方法和它创建的小部件间接创建的节点数量
  3. 缓存不变的组件
  4. 尽可能用const(这和3等价)
  5. 必须改变深度时,考虑用globalKey

将频繁更新的组件尽可能放在叶子节点

arduino 复制代码
///  * Push the state to the leaves. For example, if your page has a ticking
///    clock, rather than putting the state at the top of the page and
///    rebuilding the entire page each time the clock ticks, create a dedicated
///    clock widget that only updates itself.

ClockWidget放置在页面的中心,这样它将在页面中心显示当前时间。通过这种方式,当时钟滴答时,只有ClockWidget会重新构建,而不会重新构建整个页面,从而提高了性能和效率

import 复制代码
class ClockWidget extends StatefulWidget {
  @override
  _ClockWidgetState createState() => _ClockWidgetState();
}
class _ClockWidgetState extends State<ClockWidget> {
  String currentTime = '';
  @override
  void initState() {
    super.initState();
    // 开始时钟定时器
    startClockTimer();
  }
  @override
  void dispose() {
    // 取消时钟定时器
    stopClockTimer();
    super.dispose();
  }
  void startClockTimer() {
    // 每秒更新一次时钟时间
    Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        // 更新当前时间
        currentTime = getCurrentTime();
      });
    });
  }
  void stopClockTimer() {
    // 取消时钟定时器
    // 如果有多个定时器,确保取消所有定时器
    // timer.cancel();
  }
  String getCurrentTime() {
    // 获取当前时间的逻辑
    // 返回当前时间的字符串表示形式
  }
  @override
  Widget build(BuildContext context) {
    return Text(
      currentTime,
      style: TextStyle(fontSize: 20),
    );
  }
}
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Page'),
      ),
      body: Center(
        child: ClockWidget(), // 使用时钟小部件
      ),
    );
  }
}
void main() {
  runApp(
    MaterialApp(
      home: MyPage(),
    ),
  );
}

尽量减少由build方法和它创建的小部件间接创建的节点数量

arduino 复制代码
///  * Minimize the number of nodes transitively created by the build method and
///    any widgets it creates. Ideally, a stateful widget would only create a
///    single widget, and that widget would be a [RenderObjectWidget].
///    (Obviously this isn't always practical, but the closer a widget gets to
///    this ideal, the more efficient it will be.)

缓存不变的组件

arduino 复制代码
///  * If a subtree does not change, cache the widget that represents that
///    subtree and re-use it each time it can be used. To do this, assign
///    a widget to a `final` state variable and re-use it in the build method. It
///    is massively more efficient for a widget to be re-used than for a new (but
///    identically-configured) widget to be created. Another caching strategy
///    consists in extracting the mutable part of the widget into a [StatefulWidget]
///    which accepts a child parameter.

在示例中,我们创建了一个名为MyCachedWidget的[StatefulWidget],该小部件接受一个子部件作为参数。在_MyCachedWidgetState中,我们在initState方法中将子部件缓存到_cachedWidget变量中,并在build方法中使用该缓存的小部件。这样在每次调用build方法时,如果子树不发生变化,就会重复使用缓存的小部件,而不会重新创建一个新的小部件 。 在MyStatefulWidget中,我们使用MyCachedWidget来展示一个子树,并通过点击按钮来切换子树是否发生变化。当子树不变时,会重复使用缓存的小部件,否则会创建一个新的小部件。 通过这种缓存策略和提取可变部分的方法,可以有效地减少不必要的小部件创建,提高应用的性能。

import 复制代码
class MyCachedWidget extends StatefulWidget {
  final Widget child;
  const MyCachedWidget({required this.child});
  @override
  _MyCachedWidgetState createState() => _MyCachedWidgetState();
}
class _MyCachedWidgetState extends State<MyCachedWidget> {
  Widget? _cachedWidget; // 缓存的小部件
  @override
  void initState() {
    super.initState();
    // 初始化时,将子部件缓存起来
    _cachedWidget = widget.child;
  }
  @override
  Widget build(BuildContext context) {
    return _cachedWidget ?? Container(); // 使用缓存的小部件,如果为空则返回一个空容器
  }
}
class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool _isSubtreeUnchanged = true;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              _isSubtreeUnchanged = !_isSubtreeUnchanged; // 切换子树是否发生变化的标志
            });
          },
          child: Text('Toggle Subtree'),
        ),
        MyCachedWidget(
          child: _isSubtreeUnchanged
              ? Text('Unchanged Subtree') // 子树不变时使用缓存的小部件
              : Text('Changed Subtree'), // 子树变化时创建新的小部件
        ),
      ],
    );
  }
}
void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyStatefulWidget(),
        ),
      ),
    ),
  );
}

尽可能用const(这和3等价)

arduino 复制代码
///  * Use `const` widgets where possible. (This is equivalent to caching a
///    widget and re-using it.)

const小部件在编译时就会被创建,并在每次构建时都会重复使用相同的实例,而不是每次都创建一个新的小部件 。以下是一个示例代码,演示了如何使用const小部件:

js 复制代码
import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Text('Hello, World!'); // 使用const小部件
  }
}
void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    ),
  );
}

避免改变创建的子树的深度或改变子树中任何小部件的类型

arduino 复制代码
///  * Avoid changing the depth of any created subtrees or changing the type of
///    any widgets in the subtree. For example, rather than returning either the
///    child or the child wrapped in an [IgnorePointer], always wrap the child
///    widget in an [IgnorePointer] and control the [IgnorePointer.ignoring]
///    property. This is because changing the depth of the subtree requires
///    rebuilding, laying out, and painting the entire subtree, whereas just
///    changing the property will require the least possible change to the
///    render tree (in the case of [IgnorePointer], for example, no layout or
///    repaint is necessary at all).

避免改变创建的子树的深度或改变子树中任何小部件的类型。例如,不要返回子节点或将子节点包装在[IgnorePointer]中,而是始终将子小部件包装在[IgnorePointer]中,并控制[IgnorePointer.ignoring]属性的值。这是因为改变子树的深度需要重建、布局和绘制整个子树,而只改变属性将需要对渲染树进行最小的更改(例如,在[IgnorePointer]的情况下,根本不需要布局或重绘)。 在Flutter中,当我们需要在小部件树中进行更改时,我们应该尽量避免改变子树的深度或改变小部件的类型。这是因为改变子树的深度会导致整个子树的重建、布局和绘制,这可能会带来性能上的开销。相反,我们应该尽量保持小部件树的稳定性,只改变小部件的属性或状态。 例如,当我们想要在某些条件下使子小部件不可点击时,我们应该始终将子小部件包装在一个[IgnorePointer]小部件中,并通过控制[IgnorePointer.ignoring]属性的值来控制是否忽略点击事件。这样做只需要对渲染树进行最小的更改,不需要重新布局或重绘整个子树,从而提高性能。 以下是一个示例代码,演示了如何使用[IgnorePointer]来控制子小部件的可点击性:

js 复制代码
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
  bool _isIgnoringPointer = false;
  void toggleIgnoringPointer() {
    setState(() {
      _isIgnoringPointer = !_isIgnoringPointer;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        RaisedButton(
          child: Text('Toggle Ignoring Pointer'),
          onPressed: toggleIgnoringPointer,
        ),
        SizedBox(height: 20),
        IgnorePointer(
          ignoring: _isIgnoringPointer, // 控制 IgnorePointer 的 ignoring 属性
          child: RaisedButton(
            child: Text('Clickable Button'),
            onPressed: () {
              print('Button Clicked');
            },
          ),
        ),
      ],
    );
  }
}
void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Ignore Pointer Example'),
        ),
        body: Center(
          child: MyWidget(),
        ),
      ),
    ),
  );
}

必须改变深度时,考虑用globalKey

arduino 复制代码
///  * If the depth must be changed for some reason, consider wrapping the
///    common parts of the subtrees in widgets that have a [GlobalKey] that
///    remains consistent for the life of the stateful widget. (The
///    [KeyedSubtree] widget may be useful for this purpose if no other widget
///    can conveniently be assigned the key.)

在Flutter中,如果我们必须改变子树的深度,可以考虑将子树的公共部分包装在具有一致的[GlobalKey]的小部件中。这样做的好处是,即使改变了子树的深度,具有相同[GlobalKey]的小部件仍然可以保持它们的身份不变,从而避免了整个子树的重建和布局。

参考文档

www.youtube.com/watch?v=IOy...

相关推荐
蟾宫曲2 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心2 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455662 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029402 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟4 小时前
css文字折行以及双端对齐实现方式
前端·css
2401_882726485 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203985 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww5 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator
前端没钱5 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
m0_748255265 小时前
vue3导入excel并解析excel数据渲染到表格中,纯前端实现。
前端·excel