如何获取 StatefulWidget 的状态?

时间:2021-03-02 03:28:44

标签: flutter dart

我是 flutter 的新手,我获取 StatefulWidget 状态的方式是向小部件添加状态属性,例如:

// ignore: must_be_immutable
class _CustomContainer extends StatefulWidget {
  _CustomContainer({Key key}) : super(key: key);

  @override
  __CustomContainerState createState() {
    state = __CustomContainerState();
    return state;
  }

  __CustomContainerState state;

  void changeColor() {
    if (state == null) return;
    // call state's function
    this.state.changeColor();
  }
}

class __CustomContainerState extends State<_CustomContainer> {
  var bgColor = Colors.red;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 200,
      color: bgColor,
    );
  }

  void changeColor() {
    setState(() {
      bgColor = Colors.blue;
    });
  }
}

用法:

final redContainer = _CustomContainer();
final button = FlatButton(
      onPressed: () {
        // call widget function
        redContainer.changeColor();
      },
      child: Text('change color'),
    );

有效,但不知道有没有什么隐患?

2 个答案:

答案 0 :(得分:2)

您会注意到以命令式方式操作 Flutter 小部件非常尴尬,就像在问题中一样。这是因为 Flutter 在构建屏幕时采用了声明式方法。

声明式与命令式

Flutter UI 的方法/哲学是声明式 UI 与命令式 UI。

上述问题中的示例倾向于命令式方法。

  • 创建一个对象
  • 对象保存状态(信息)
  • 对象暴露方法
  • 使用方法对对象进行更改 → UI 更改

声明式方法:

  • 您的对象上方有状态(信息)
  • 您的对象是从该状态声明(创建)
  • 如果状态改变...
  • 您的对象将使用更改后的状态重新创建

下面我尝试将上面的命令式方法转换为声明式方法。

CustomContainer 是用 color 声明;状态已知/保持在CustomContainer之外并在其构造中使用。

施工后,您不能强加CustomContainer 进行颜色更改。在命令式框架中,您将公开一个方法 changeColor(color) 并调用该方法,该框架将执行魔术以显示新颜色。

在 Flutter 中,要更改 CustomContainer 的颜色,您声明 CustomContainer 为新颜色。

import 'package:flutter/material.dart';

/// UI object holds no modifiable state.
/// It configures itself once based on a declared color.
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
  final Color color; 

  CustomContainer(this.color);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: color,
      child: Text('this is a colored Container'),
    );
  }
}

/// A StatefulWidget / screen to hold state above your UI object
class DeclarativePage extends StatefulWidget {
  @override
  _DeclarativePageState createState() => _DeclarativePageState();
}

class _DeclarativePageState extends State<DeclarativePage> {
  var blue = Colors.blueAccent.withOpacity(.3);
  var red = Colors.redAccent.withOpacity(.3);
  
  Color color;
  // state (e.g. color) is held in a context above your UI object
  
  @override
  void initState() {
    super.initState();
    color = blue;
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Declarative Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CustomContainer(color),
            // color "state" ↑ is passed in to build/rebuild your UI object 
            RaisedButton(
              child: Text('Swap Color'),
                onPressed: () {
                  setState(() {
                    toggleColor();
                  });
                }
            )
          ],
        ),
      ),
    );
  }

  void toggleColor() {
    color = color == blue ? red : blue;
  }
}

declarative vs imperative on Flutter.dev 上阅读更多内容。

setState() 重建和性能

在性能方面,当一个小部件(位于小部件树的下方)需要重建时,重建整个屏幕似乎很浪费。

如果可能(并且明智),最好将具有状态和需要重建的特定元素包装在 StatefulWidget 中,而不是将整个页面包装在 StatefulWidget 中并重建所有内容。 (很可能,这甚至不会成为问题,我将在下面进一步讨论。)

下面我修改了上面的示例,将 StatefulWidget 从整个 DeclarativePage 移动到 ChangeWrapper 小部件。

ChangeWrapper 将包装 CustomContainer(改变颜色)。

DeclarativePage 现在是一个 StatelessWidget,在切换 CustomContainer 的颜色时不会重建。

import 'package:flutter/material.dart';

class ChangeWrapper extends StatefulWidget {
  @override
  _ChangeWrapperState createState() => _ChangeWrapperState();
}

class _ChangeWrapperState extends State<ChangeWrapper> {
  final blue = Colors.blueAccent.withOpacity(.3);
  final red = Colors.redAccent.withOpacity(.3);

  Color _color; // this is state that changes

  @override
  void initState() {
    super.initState();
    _color = blue;
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        CustomContainer(_color),
        RaisedButton(
            child: Text('Swap Color'),
            onPressed: () {
              setState(() {
                toggleColor();
              });
            }
        )
      ],
    );
  }

  void toggleColor() {
    _color = _color == blue ? red : blue;
  }
}

/// UI object holds no state, it configures itself once based on input (color).
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
  final Color color;

  CustomContainer(this.color);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: color,
      child: Text('this is a colored Container'),
    );
  }
}

class DeclarativePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    print('Declarative Page re/built');
    return Scaffold(
      appBar: AppBar(
        title: Text('Declarative Page'),
      ),
      body: Center(
        child: ChangeWrapper(),
      ),
    );
  }
}

在运行此版本的代码时,通过按下按钮交换颜色时,仅重建 ChangeWrapper 小部件。您可以查看“声明性页面重新/构建”的控制台输出,该输出仅在第一次屏幕构建/查看时写入调试控制台一次。

如果 DeclarativePage 很大,有数百个小部件,以上述方式隔离小部件重建可能很重要或有用。在小屏幕上,例如顶部的第一个示例,甚至是带有几十个小部件的平均屏幕,节省的差异可能可以忽略不计。

Flutter 旨在以每秒 60 帧的速度运行。如果您的屏幕可以在 16 毫秒内构建/重建所有小部件(1000 毫秒/60 帧 = 每帧 16.67 毫秒),那么用户将不会看到任何问题。

当您使用动画时,这些动画设计为以每秒 60 帧(滴答)的速度运行。即动画中的小部件将在动画运行的每一秒内重建 60 次。

这是正常的 Flutter 操作,它是为它设计和构建的。因此,当您考虑是否可以优化您的小部件架构时,考虑其上下文或该组小部件将如何使用是很有用的。如果小部件组不在动画中,则在每个屏幕渲染或每个人点击按钮时构建一次...优化可能不是一个大问题。

动画中的一大组小部件......很可能是考虑优化和性能的好选择。

顺便说一句,this video series is a good overview of the Flutter architecture。我猜 Flutter 有很多隐藏的优化,例如当 Widget 没有实质性/实质性改变时重新使用 Element,以节省 CPU 周期实例化、渲染和定位 Element 对象。

答案 1 :(得分:1)

在要添加状态的地方添加 setState() 方法

相关问题