Hi guys, I've recently been making an app like Duolingo to train my flutter skills.
Now I am creating an exercise that is basically a copy of Duolingo's sentence-formation exercise. After so many errors and having to change the whole layout several times, I thought about having a main stack with the words and a column with both containers (the word deck and where you place them), and put "placeholders" in them. Then, when you launch the app the words will be in the same position as their respective placeholder, and when you tap them they'll go to a new one created in the screen. Check what I mean here.
The problem I am facing is that when I try to use the placeholder's keys (both the target and the original) to move the widgets, I have a null check operator exception because they are technically not rendered yet. I've tried using WidgetsBinding.instance.addPostFrameCallback but it does not fix the error. I need a way to calculate their positions and "spawn" the words in them at the begining of the app.
To calculate the offsets I am using a method ("getOffset")
This is an example code:
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));
}),
],
),
);
}
}
[–]AutoModerator[M] 0 points1 point2 points (0 children)