【问题标题】:Flutter setState of child widget without rebuilding parent子小部件的颤振setState而不重建父小部件
【发布时间】:2019-01-14 10:34:26
【问题描述】:

我有一个包含listViewfloatingActionButton 的父级我想在用户开始滚动时隐藏floatingActionButton 我已经设法在父级小部件中执行此操作,但这需要重新构建列表每一次。

我已将floatingActionButton 移至单独的类,因此我可以更新状态并仅重建该小部件我遇到的问题是将数据从父类中的ScrollController 传递给子类,这很简单通过导航来完成,但如果不重建父级,接缝会更尴尬!

【问题讨论】:

  • 你能发布你的代码吗?
  • 您可以为 FAB 使用 GlobalKey,然后使用它从 ScrollController 访问 FAB。这是一种不被鼓励的模式,但可能适合您的需求。一种在架构上更合理的方法是使用 Flutter Redux (pub.dartlang.org/packages/flutter_redux) 之类的东西来响应操作。
  • OP,您确定 setState 确实导致了性能问题吗?晶圆厂只会偶尔消失/重新出现。 Flutter 针对这类东西进行了优化。小部件非常轻量级,当您调用 setState 时,不会释放/重建底层的重型 UI 对象。
  • @boformer 我的印象是 setState 的目的是重建 UI 已经发生了变化 docs.flutter.io/flutter/widgets/State/setState.html 但要回答这个问题,不,我不能确定我只是在学习颤振,它看起来喜欢它的剂量与我期望的有点不同,我应该或可以向父母请求重建吗?我确实注意到,当我进行实时重新加载时,如果我在实时重新加载之前向下滚动页面,状态有时会发生变化!
  • 重建的问题在于它的速度很慢,因此由于重建需要很长时间,因此不值得隐藏按钮并查看他们所说的一些关于不重建的谷歌教程不需要像父级一样重建的元素列表中没有任何变化,但它仍在重建我不是 100% 确定,但这可能是因为它是从 firestore 构建的!也许我应该保存列表的本地副本并进行比较...无论如何我已经接受了您的回答,谢谢。

标签: android dart flutter


【解决方案1】:

为了获得最佳性能,您可以围绕Scaffold 创建自己的包装器,以获取body 作为参数。在HideFabOnScrollScaffoldState 中调用setState 时,不会重建body 小部件。

这是一种常见模式,也可以在 AnimationBuilder 等核心小部件中找到。

import 'package:flutter/material.dart';

main() => runApp(MaterialApp(home: MyHomePage()));

class MyHomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  ScrollController controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return HideFabOnScrollScaffold(
      body: ListView.builder(
        controller: controller,
        itemBuilder: (context, i) => ListTile(title: Text('item $i')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
      controller: controller,
    );
  }
}

class HideFabOnScrollScaffold extends StatefulWidget {
  const HideFabOnScrollScaffold({
    Key key,
    this.body,
    this.floatingActionButton,
    this.controller,
  }) : super(key: key);

  final Widget body;
  final Widget floatingActionButton;
  final ScrollController controller;

  @override
  State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}

class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
  bool _fabVisible = true;

  @override
  void initState() {
    super.initState();
    widget.controller.addListener(_updateFabVisible);
  }

  @override
  void dispose() {
    widget.controller.removeListener(_updateFabVisible);
    super.dispose();
  }

  void _updateFabVisible() {
    final newFabVisible = (widget.controller.offset == 0.0);
    if (_fabVisible != newFabVisible) {
      setState(() {
        _fabVisible = newFabVisible;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: widget.body,
      floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
    );
  }
}

或者,您也可以为FloatingActionButton 创建一个包装器,但这可能会中断转换。

【讨论】:

  • 谢谢,这是我一直在寻找的,它可以完美地工作,而无需每次都重新构建列表。这条线需要从 == 编辑到 != code void _updateFabVisible() { final newFabVisible = (widget.controller.offset == 0.0); if (_fabVisible != newFabVisible) { setState(() { _fabVisible = newFabVisible; }); } } code
  • 如何检测偏移量何时 = 50 ?当滚动太快时,滚动偏移有时会跳过偏移
【解决方案2】:

我认为使用流更简单,也很容易。

您只需要在事件到达时发布到流,然后使用流构建器来响应这些更改。

在这里,我根据小部件层次结构中小部件的焦点显示/隐藏组件。

我在这里使用了 rxdart 包,但我认为您不需要。你也可能想要,因为大多数人无论如何都会使用 BloC 模式。

import 'dart:async';
import 'package:rxdart/rxdart.dart';

class _PageState extends State<Page> {
  final _focusNode = FocusNode();

  final _focusStreamSubject = PublishSubject<bool>();
  Stream<bool> get _focusStream => _focusStreamSubject.stream;

  @override
  void initState() {
    super.initState();

    _focusNode.addListener(() {
      _focusStreamSubject.add(_focusNode.hasFocus);
    });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          _buildVeryLargeComponent(),
          StreamBuilder(
            stream: _focusStream,
            builder: ((context, AsyncSnapshot<bool> snapshot) {
              if (snapshot.hasData && snapshot.data) {
                return Text("keyboard has focus")
              }
              return Container();
            }),
          )
        ],
      ),
    );
  }
}

【讨论】:

  • 我很难理解流生成器。您的回答非常简单易懂。非常感谢。
  • 你的代码有很多错误,不完整,但是你的想法很好
  • @MohsenEmami 请详细说明。你的意思是没有声明 _buildVeryLargeComponent 函数?还是为了简洁而省略了该组件?这是一个非常普通的有状态组件。
【解决方案3】:

当父级中的值发生更改时,重建仅子级小部件的一种好方法是使用ValueNotifierValueListenableBuilder。将ValueNotifier 的实例添加到父状态类中,并将要重建的小部件包装在ValueListenableBuilder 中。

当您想更改值时,请使用通知器而不调用setState,然后使用新值重建子小部件。

import 'package:flutter/material.dart';

class Parent extends StatefulWidget {
  @override
  _ParentState createState() => _ParentState();
}

class _ParentState extends State<Parent> {
  ValueNotifier<bool> _notifier = ValueNotifier(false);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
        ValueListenableBuilder(
            valueListenable: _notifier,
            builder: (BuildContext context, bool val, Widget child) {
              return Text(val.toString());
            }),
      ],
    );
  }

  @override
  void dispose() {
      _notifier.dispose();

      super.dispose();
  }
}

【讨论】:

  • 这应该是新接受的答案。这正是我们所需要的。
  • 只有几行代码。这很棒。
  • +1。请注意,它确实在内部调用了 setState()。在进行重绘时,您可能会遇到此内部 setState 的错误。
  • 这是完美的。我尝试查看 Bloc 提供程序、Stream 构建器,甚至只是手动调用 setState() 的混乱回调,但使用 ValueListener 作为包装器是简单重建子小部件的完美平衡。谢谢你,我希望我能马上找到这个。
【解决方案4】:

您可以使用StatefulBuilder 并使用其 setState 函数在其下构建小部件。

例子:

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {

  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // put widget here that you do not want to update using _setState of StatefulBuilder
        Container(
          child: Text("I am static"),
        ),
        StatefulBuilder(builder: (_context, _setState) {
          // put widges here that you want to update using _setState
          return Column(
            children: [
              Container(
                child: Text("I am updated for $count times"),
              ),
              RaisedButton(
                  child: Text('Update'),
                  onPressed: () {
                    // Following only updates widgets under StatefulBuilder as we are using _setState
                    // that belong to StatefulBuilder
                    _setState(() {
                      count++;
                    });
                  })
            ],
          );
        }),
      ],
    );
  }
}

【讨论】:

  • 我将如何从 StatefulBuilder 外部调用孩子的 setState
  • @NullByte08 您可以尝试将 _setState 分配给全局变量。但是,这可能不是正确的方法。考虑使用流。看看大卫里斯的答案。
猜你喜欢
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 2020-09-07
  • 2021-11-12
  • 2020-10-18
  • 2019-08-31
  • 2019-12-27
  • 2018-10-24
相关资源
最近更新 更多