WidgetsBinding 的 addPostFrameCallback 无法按预期工作

问题描述 投票:0回答:1

在我的代码中,我有一个方法可以根据 Widget 的键确定其位置,我在调用 onTap 时调用此函数。问题是我的代码在启动时崩溃,因为它试图获取未渲染的小部件位置。

现在我正在创建一个练习,基本上是 Duolingo 的句子形成练习的副本。在经历了这么多错误并且不得不多次更改整个布局之后,我考虑使用一个包含单词的主堆栈和一个包含两个容器(单词甲板和放置它们的位置)的列,并在其中放置“占位符”。然后,当您启动应用程序时,这些单词将位于与其各自占位符相同的位置,并且当您点击它们时,它们将转到屏幕中创建的新单词。就像移动 p 检查我的意思这里

我面临的问题是,当我尝试使用占位符的键(目标键和原始键)来移动小部件时,我遇到了空检查运算符异常,因为从技术上讲它们尚未呈现。我尝试过使用 WidgetsBinding.instance.addPostFrameCallback 但它不能修复错误。我需要一种方法来计算它们的位置并在应用程序开始时“生成”其中的单词。

为了计算偏移量,我使用了一种方法(“getOffset”)

这是示例代码:

方法是这样的:

Offset getOffset(GlobalKey key) {
    Offset offset = Offset.zero;

    BuildContext context = key.currentContext!;  //<----- This is where the exception is thrown

    RenderBox renderBox = context.findRenderObject() as RenderBox;
    offset = renderBox.localToGlobal(Offset.zero);

    return offset;
}

这是我的代码:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Animation Proof of Concept',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.black),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Container xo =
      Container(color: Colors.white, width: 65, height: 65, child: Text("Xo"));
  Container vo =
      Container(color: Colors.white, width: 65, height: 65, child: Text("Vo"));

  List<Container> letters = [];
  List<Widget> targets = [];
  Map<int, GlobalKey> targetKeys = {};
  Map<int, GlobalKey> homeKeys = {};
  List<bool> isMovedList = [];

  @override
  void initState() {
    super.initState();
    letters = [vo];

    isMovedList = [for (var widget in letters) false];

    targetKeys = {
      for (var widget in letters)
        letters.indexOf(widget):
            GlobalKey(debugLabel: "targetKey${letters.indexOf(widget)}")
    };
    homeKeys = {
      for (var widget in letters)
        letters.indexOf(widget):
            GlobalKey(debugLabel: "homeKey${letters.indexOf(widget)}")
    };
  }

  getOffset(GlobalKey key) {
    Offset offset = Offset.zero;
    BuildContext context = key.currentContext!;
    RenderBox renderbox = context.findRenderObject() as RenderBox;
    offset = renderbox.localToGlobal(Offset.zero);
    return offset;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.purple,
      body: Stack(
        children: [
          Column(
            children: [
              Expanded(
                  child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: targets,
              )),
              Expanded(
                  child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: letters.map((letter) {
                  return Opacity(
                    key: homeKeys[letters.indexOf(letter)],
                    opacity: 0.2,
                    child: letter,
                  );
                }).toList(),
              ))
            ],
          ),
          ...letters.map((letter) {
            Offset targetOffset = Offset.zero;
            Offset homeOffset = Offset.zero;
            return AnimatedPositioned(
                left: isMovedList[letters.indexOf(letter)] ? 312.5 : 312.5,
                top: isMovedList[letters.indexOf(letter)] ? 235 : 55,
                duration: const Duration(milliseconds: 250),
                child: GestureDetector(
                    onTap: () {
                      setState(() {
                        WidgetsBinding.instance.addPostFrameCallback((_) {});
                        isMovedList[letters.indexOf(letter)] =
                            !isMovedList[letters.indexOf(letter)];
                        if (isMovedList[letters.indexOf(letter)] == false) {
                          targets.add(Container(
                              key: targetKeys[letters.indexOf(letter)],
                              color: Colors.red,
                              width: 65,
                              height: 65,
                              child: Center(
                                  child: Text(
                                "T${letters.indexOf(letter)}",
                                style: TextStyle(
                                    fontWeight: FontWeight.bold, fontSize: 20),
                              ))));
                        } else {
                          targets.removeWhere((element) =>
                              element.key ==
                              targetKeys[letters.indexOf(letter)]);
                        }
                      });
                    },
                    child: letter));
          }),
        ],
      ),
    );
  }
}

我尝试搬迁

WidgetsBinding.instance.addPostFramCallback()
但它 还是不行。

flutter dart
1个回答
0
投票

我的代码有几个问题,

  1. 我试图获取 targetKeys(来自仅当我点击另一个小部件时才创建的小部件)。因此,当尝试计算 Offset 时,它为空。

修复:将 targetKeys 的偏移量计算包装在 if 语句中:

WidgetsBinding.instance.addPostFrameCallback((_) {
                  setState(() {
                    for (int i = 0; i < letters.length; i++) {
                      if (targetKeys[i]?.currentContext != null) {
                        targetOffsets[i] = getOffset(targetKeys[i]!);
                      }
                    }
                  });
                });
  1. 必须在
    WidgetsBinding.instance.addPostFrameCallback()
    中计算
    initState(){}
    才能正常工作。 修复:将其添加到 `initState(){}

我创建了两个 Map 来存储 targetOffsets 和 homeOffsets,其偏移量与键的 Map 相同,默认值为 Offset.zero。因此,在运行代码的第一秒(并非创建每个小部件),有一个默认值。这就是导致它不可能实现的主要错误。 修复这些错误并将代码调整得更好后,它按预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.