【问题标题】:Flutter: Use AbsorbPointer without rebuilding entire widget treeFlutter:使用 AbsorbPointer 无需重建整个小部件树
【发布时间】:2021-10-03 16:34:44
【问题描述】:

我有一个有状态的主页,其中包含有状态小部件子项的列表。当我点击一个孩子时,我会调用它的setState() 来为它添加一个CircularProgressIndicator。这一切都很好,花花公子;点击一个孩子只会重建那个孩子。

但是,我的主页也包含在 AbsorbPointer 中,并且我想在单击子小部件时设置 absorbing = true。目标是阻止用户在应用程序在后台执行一些异步工作时四处点击。现在的问题是,如果我在主页调用setState() 将“吸收”设置为true,它将重建所有子小部件。

我可以将一些参数传递给子窗口小部件,这样只有我点击的那个会有一个 CircularProgressIndicator,但即便如此,所有其他孩子仍然会被重建。

我想这归结为这样一个事实,即我无法在父小部件上调用 setState() 而不重建所有子小部件,即使我传递给该 setState() 的参数 (absorbing) 无关和那些孩子。

有解决办法吗?

谢谢!

// home_screen.dart

class HomeScreen extends StatefulWidget {
  static const String routeName = "homeScreen";
  final MyUser? user;

  const HomeScreen({Key? key, required this.user}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
  final MyDatabase _db = MyDatabase();
  MyUser? _me;

  int _currentPage = -1;
  bool _isLoading = false;

  ...

  @override
  Widget build(BuildContext context) {
    return AbsorbPointer(
      absorbing: _isLoading,
      child: Container(
        decoration: BoxDecoration(
          // color: Color(0xFF0d0717),
          image: DecorationImage(
            image: Image.asset(
              'assets/background.png',
            ).image,
            fit: BoxFit.cover,
          ),
        ),
        child: Scaffold(
          backgroundColor: Colors.transparent,
          appBar: ...,
          bottomNavigationBar: ...,
          body: SingleChildScrollView(
            child: Container(
              padding: const EdgeInsets.symmetric(
                vertical: 8.0,
                horizontal: 12.0,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  StreamBuilder<QuerySnapshot>(
                    stream: _db.getLiveChannels(),
                    builder: (_, snapshot) {
                      if (!snapshot.hasData) {
                        // print("Has no data");
                        return Center(
                          child: CircularProgressIndicator(),
                        );
                      }
                      _channels.addAll(List.generate(
                          snapshot.data!.docs.length,
                          (index) => Channel.fromSnapshot(
                              snapshot.data!.docs[index])));
                      return Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            children: [
                              Text(
                                'Now Playing',
                                style: TextStyle(
                                  fontSize: 24.0,
                                  fontWeight: FontWeight.w700,
                                ),
                              ),
                              SizedBox(width: 8.0),
                              LiveIndicator(),
                            ],
                          ),
                          SizedBox(height: 8.0),
                          Container(
                            height: 250,
                            child: PageView.builder(
                              physics: PageScrollPhysics(),
                              scrollDirection: Axis.horizontal,
                              controller: PageController(
                                viewportFraction: .9,
                              ),
                              itemCount: _channels.length,
                              itemBuilder: (context, index) {
                                Channel channel =
                                    _channels[_channels.length - 1 - index];
                                return ChildWidget(
                                  callback: _callback;
                                  loading: (_isLoading && _currentPage == index),
                                  key: UniqueKey(),
                                );
                              },
                            ),
                          ),
                          ...,
                        ],
                      );
                    },
                  ),
                  ...,
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Future<void> _callback(params) async {
    if (_isLoading == false) {
      setState(() {
        _isLoading = true;
        _currentPage = index;
      });
    }
    someAsyncMethod().then((_) => setState(() {
          _isLoading = false;
          _currentPage = -1;
        }));
  }

}

// child_widget.dart

class ChildWidget extends StatefulWidget {
  final Future<void> Function(params) callback;
  final bool loading;

  const ChildWidget({
    Key? key,
    required this.callback,
    required this.loading,
  }) : super(key: key);

  @override
  _ChildWidgetState createState() => _ChildWidgetState();
}

class _ChildWidgetState extends State<ChildWidget> {
  late Future<void> Function(params) callback;

  late bool loading;

  @override
  void initState() {
    super.initState();
    callback = widget.callback;
    loading = widget.loading;
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      child: Column(
        children: [
          Expanded(
            child: CustomClickableWidget(
              onPressed: callback,
              child: Expanded(
                child: Container(
                  child: Stack(
                    children: [
                      ...,
                      if (loading) ...[
                        Container(
                          alignment: Alignment.center,
                          child: CircularProgressIndicator(),
                        ),
                      ],
                    ],
                  ),
                ),
              ),
            ),
          ),
          ...,
        ],
      ),
    );
  }
}

Screenshot

【问题讨论】:

    标签: flutter dart setstate


    【解决方案1】:

    SetState 函数会触发 Build() 函数,因此 Build() 函数中的所有代码都将再次执行。我不太明白为什么这对你来说是个问题?

    另一方面,在您的代码中,我看到您为您的孩子定义了一个键:UniqueKey ()。当构建函数将在 SetState() 之后运行时,它将创建一个新的子节点,而不保留前一个子节点的状态。你不应该在你的函数中定义 UniqueKey () 而是作为你的状态的一个实例变量

    ChildWidget(callback: _callback;
                loading: (_isLoading && _currentPage == index),
                key: UniqueKey(),
    )
    

    你应该在这里定义你的密钥

    class _ChildWidgetState extends State<ChildWidget> {
    UniqueKey myKey = UniqueKey();
    

    你的功能

    ChildWidget(callback: _callback;
                loading: (_isLoading && _currentPage == index),
                key: myKey,
    )
    

    【讨论】:

    • 感谢您的评论!问题是我想以某种方式仅重建我单击的子项,以便它显示加载指示器(如屏幕截图中所示),但还要更改父小部件中 AbsorbPointer 的 absorbing 参数。似乎没有办法,所以我想我正在寻找一种解决方法。我的目标是 1) 表明我点击了特定的孩子,2) 阻止用户在后台任务运行时点击/滚动应用程序。
    • 去掉Build()函数中uniqueKey的创建。我还看到您在 childwidget 的 initState 中初始化了变量“Loading”。因此,这个变量只定义一次,在你的孩子重建期间不会改变,问题也可能来自那里
    • 它现在工作了!我不知道是因为我按照你说的那样删除了 uniqueKey,还是因为我的 await/async 之前出现了一些问题。但是在修复它之后,我现在可以在子进程中调用 setState() 来切换其加载指示器,并在父进程中调用 setState() 来切换其 _isLoading 变量。
    猜你喜欢
    • 2021-06-05
    • 2021-07-03
    • 1970-01-01
    • 2021-06-11
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    • 2021-10-29
    相关资源
    最近更新 更多