I have a working snippet that I've wrote, but I kinda don't understand how flutter is (re)using the widgets creating in the build method:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyGame());
}
class MyGame extends StatelessWidget {
const MyGame({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: GameWidget());
}
}
class GameWidget extends StatefulWidget {
const GameWidget({Key? key}) : super(key: key);
static const squareWidth = 50.0;
static const squareHeight = 50.0;
#override
State<GameWidget> createState() => _GameWidgetState();
}
class _GameWidgetState extends State<GameWidget> {
List<Offset> offsets = [];
#override
Widget build(BuildContext context) {
if (offsets.isEmpty) {
for(int i = 0; i < 20; i++) {
offsets.add(calculateNextOffset());
}
}
List<Widget> squareWidgets = [];
for (int j = 0; j < offsets.length; j++) {
squareWidgets.add(AnimatedPositioned(
left: offsets[j].dx,
top: offsets[j].dy,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 500),
child: GestureDetector(
onTapDown: (tapDownDetails) {
setState(() {
offsets.removeAt(j);
for (int k = 0; k < offsets.length; k++) {
offsets[k] = calculateNextOffset();
}
});
},
behavior: HitTestBehavior.opaque,
child: Container(
width: GameWidget.squareWidth,
height: GameWidget.squareHeight,
color: Colors.blue,
),
),
));
}
return Stack(
children: squareWidgets,
);
}
Offset calculateNextOffset() {
return randomOffset(
MediaQuery.of(context).size,
const Size(GameWidget.squareWidth, GameWidget.squareHeight),
MediaQuery.of(context).viewPadding.top);
}
double randomNumber(double min, double max) =>
min + Random().nextDouble() * (max - min);
Offset randomOffset(
Size parentSize, Size childSize, double statusBarHeight) {
var parentWidth = parentSize.width;
var parentHeight = parentSize.height;
var randomPosition = Offset(
randomNumber(parentWidth, childSize.width),
randomNumber(statusBarHeight,parentHeight - childSize.height),
);
return randomPosition;
}
}
Every time I click on a container, i expect my "offsets" state to be updated, but I also expect all the AnimationPositioned widgets, GestureDetector widgets and the square widgets that you see would be rerendered.
With rerendered i mean they would disappear from the screen and new ones would be rerendered (and the animation from the first widgets would be cancelled and never displayed)
However it works? Could someone explain this to me?
EDIT: I've updated my snippet of code in my question to match what i'm asking, which i'm also going to rephrase here:
Every time I click on a square, i want that square to disappear and all the other square to randomly animate to another position. But every time I click on a square, another random square is deleted, and the one i'm clicking is animating.
I want the square that I click on disappears and the rest will animate.
Following up from the comments - Actually, the square you click is disappears. However, to see this visually add this to your container color:
color:Colors.primaries[Random().nextInt(Colors.primaries.length)],
Now, your offsets are generating just fine and random. However, because you have an AnimatedContainer widget. This widget will remember the last x & y position of your square and animate the new square starting from that old x,y value to the new on you passed it. So if you really want the square you click on disappear - you will need to either use Positioned widget:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyGame());
}
class MyGame extends StatelessWidget {
const MyGame({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: GameWidget());
}
}
class GameWidget extends StatefulWidget {
const GameWidget({Key? key}) : super(key: key);
static const squareWidth = 100.0;
static const squareHeight = 100.0;
#override
State<GameWidget> createState() => _GameWidgetState();
}
class _GameWidgetState extends State<GameWidget> {
List<Offset> offsets = [];
#override
Widget build(BuildContext context) {
if (offsets.isEmpty) {
offsets.add(calculateNextOffset());
}
print(offsets);
List<Widget> squareWidgets = [];
for (var offset in offsets) {
squareWidgets.add(Positioned(
left: offset.dx,
top: offset.dy,
//curve: Curves.easeIn,
//duration: const Duration(milliseconds: 500),
child: GestureDetector(
onTapDown: (tapDownDetails) {
setState(() {
for (var i = 0; i < offsets.length; i++) {
offsets[i] = calculateNextOffset();
}
offsets.add(calculateNextOffset());
});
},
behavior: HitTestBehavior.opaque,
child: Container(
width: GameWidget.squareWidth,
height: GameWidget.squareHeight,
color:Colors.primaries[Random().nextInt(Colors.primaries.length)],
),
),
));
}
return Stack(
children: squareWidgets,
);
}
Offset calculateNextOffset() {
return randomOffset(
MediaQuery.of(context).size,
const Size(GameWidget.squareWidth, GameWidget.squareHeight),
MediaQuery.of(context).viewPadding.top);
}
double randomNumber(double min, double max) =>
min + Random().nextDouble() * (max - min);
Offset randomOffset(
Size parentSize, Size childSize, double statusBarHeight) {
var parentWidth = parentSize.width;
var parentHeight = parentSize.height;
var randomPosition = Offset(
randomNumber(parentWidth, childSize.width),
randomNumber(statusBarHeight,parentHeight - childSize.height),
);
return randomPosition;
}
}
If you want the rest of the squares to animate while only the one that is clicked disappears. You will need to rethink your implementation and track all square perhaps using unique keys and custom animations. Hope that helps!
I've finally found it:
In the context of the snippet inside the question: Every time you click on a square, it will correctly remove that item, but the widgets are rerendered from that new list, and the last widget that was previously rendered will be removed instead of the one that I clicked.
This has to do because every widget in the widget tree is rendered as an element inside the element tree. If the state of the element inside the element tree is the same, it will not rerender that one. and they are all just blue squares in the end, so there is no distinction.
You can find a very nice video made by the flutter devs here:
When to Use Keys - Flutter Widgets 101 Ep. 4
Long story short: Here is the snippet with the fix, which is to add a Key to each widget, then the state will change on the element inside the element tree and it will rerender (and remove) the correct widgets/elements:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyGame());
}
class MyGame extends StatelessWidget {
const MyGame({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: GameWidget());
}
}
class GameWidget extends StatefulWidget {
const GameWidget({Key? key}) : super(key: key);
static const squareWidth = 50.0;
static const squareHeight = 50.0;
#override
State<GameWidget> createState() => _GameWidgetState();
}
class _GameWidgetState extends State<GameWidget> {
List<OffsetData> offsets = [];
#override
Widget build(BuildContext context) {
if (offsets.isEmpty) {
for(int i = 0; i < 20; i++) {
offsets.add(OffsetData(UniqueKey(), calculateNextOffset()));
}
}
List<Widget> squareWidgets = [];
for (int j = 0; j < offsets.length; j++) {
squareWidgets.add(AnimatedPositioned(
key: offsets[j].key, // This line is the trick
left: offsets[j].offset.dx,
top: offsets[j].offset.dy,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 500),
child: GestureDetector(
onTapDown: (tapDownDetails) {
setState(() {
offsets.removeAt(j);
for (var offsetData in offsets) {
offsetData.offset = calculateNextOffset();
}
});
},
behavior: HitTestBehavior.opaque,
child: Container(
width: GameWidget.squareWidth,
height: GameWidget.squareHeight,
color: Colors.blue,
),
),
));
}
return Stack(
children: squareWidgets,
);
}
Offset calculateNextOffset() {
return randomOffset(
MediaQuery.of(context).size,
const Size(GameWidget.squareWidth, GameWidget.squareHeight),
MediaQuery.of(context).viewPadding.top);
}
double randomNumber(double min, double max) =>
min + Random().nextDouble() * (max - min);
Offset randomOffset(
Size parentSize, Size childSize, double statusBarHeight) {
var parentWidth = parentSize.width;
var parentHeight = parentSize.height;
var randomPosition = Offset(
randomNumber(parentWidth, childSize.width),
randomNumber(statusBarHeight,parentHeight - childSize.height),
);
return randomPosition;
}
}
class OffsetData {
Offset offset;
final Key key;
OffsetData(this.key, this.offset);
}
Related
I attempted to detect diagonal swipe direction using GestureDetector. I used the onPanStart method to detect the screen's corners.
However, it is not perfect because it only considers the beginning of the gesture.
Right now, if I swipe bottom left to top right in the top right corner or part, it gives me the incorrect top right direction. In this case, the correct direction should be bottom left.
So, how can we identify diagonal swipe direction using GestureDetector or any other method?
I looked at several other StackOverflow posts, but they only detect top, left, right, and bottom directions.
Here is an example of code.
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
String? swipeDirection;
return Scaffold(
body: SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
child: Container(),
onPanStart: (details) {
final halfScreenWidth = screenSize.width / 2;
final halfScreenHeight = screenSize.height / 2;
final position = details.localPosition;
if ((position.dx < halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeDirection = 'topLeft';
} else if ((position.dx > halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeDirection = 'topRight';
} else if ((position.dx < halfScreenWidth) &&
(position.dy > halfScreenHeight)) {
swipeDirection = 'bottomLeft';
} else {
swipeDirection = 'bottomRight';
}
},
),
),
));
}
}
You can use pan update to find the position of the pointer the same manner you did for pan start and get the starting and ending points. Then on pan end execute with the final results
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHome(),
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
#override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
String? swipeStart;
String? swipeEnd;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.grey,
),
onPanStart: (details) {
final halfScreenWidth = screenSize.width / 2;
final halfScreenHeight = screenSize.height / 2;
final position = details.localPosition;
if ((position.dx < halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeStart = 'topLeft';
} else if ((position.dx > halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeStart = 'topRight';
} else if ((position.dx < halfScreenWidth) &&
(position.dy > halfScreenHeight)) {
swipeStart = 'bottomLeft';
} else {
swipeStart = 'bottomRight';
}
// print(swipeStart);
},
onPanUpdate: (details) {
final halfScreenWidth = screenSize.width / 2;
final halfScreenHeight = screenSize.height / 2;
final position = details.localPosition;
if ((position.dx < halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeEnd = 'topLeft';
} else if ((position.dx > halfScreenWidth) &&
(position.dy < halfScreenHeight)) {
swipeEnd = 'topRight';
} else if ((position.dx < halfScreenWidth) &&
(position.dy > halfScreenHeight)) {
swipeEnd = 'bottomLeft';
} else {
swipeEnd = 'bottomRight';
}
//print(swipeEnd);
},
onPanEnd: (details) {
print("Final direction was from $swipeStart to $swipeEnd");
},
),
),
);
}
}
I have printed output like this
flutter: Final direction was from topLeft to bottomRight
flutter: Final direction was from bottomLeft to topRight
EDIT
You can find the offset when the pan starts and updates. Then on pan end calculate the direction like this
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHome(),
);
}
}
class MyHome extends StatefulWidget {
const MyHome({Key? key}) : super(key: key);
#override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
String? horizontalDirection;
String? verticalDirection;
late Offset startPoint;
late Offset endPoint;
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.grey,
),
onPanStart: (details) {
startPoint = details.localPosition;
},
onPanUpdate: (details) {
endPoint = details.localPosition;
},
onPanEnd: (details) {
if (startPoint.dx < endPoint.dx) {
horizontalDirection = "right";
} else {
horizontalDirection = "left";
}
if (startPoint.dy < endPoint.dy) {
verticalDirection = "bottom";
} else {
verticalDirection = "top";
}
print("Final direction was $horizontalDirection$verticalDirection");
},
),
),
);
}
}
Looking into the GestureDetector widget there isn't any kind of feature your'e searching for. Also there is no other widget in my mind which is able to do that.
Maybe you can try to add the following:
Save the position of the onPanStart of one of the directions. Add onPanEnd and do the exact same you did for onPanStart and also save these positions. Now add onPanUpdate and with this you will compare the first and last point which was recognized. If you are working with screensizes as I can see, you can divide your screen in 4 blocks (top, left, right, bottom) and with the informations of the first and last position you can detect the diagonal swipe
I'm currently building an App that has a lot of vector lines that I want to animate.
I have tried using flutter_svg but ultimatly wasn't able to make individual lines tapable because only the top svg inside the stack is changed.
The new solution was to use this tool: https://fluttershapemaker.com/ which is recommended by flutter_svg to use.
This converted my SVG to about 4000 lines of geometry data.
I started to add an animation that lets all lines glow up and then go back to darkness.
But the performance is quite terrible.
It starts with about 10 fps and after a minute or so goes down to 0.5fps and lower.
This is mainly due to the rendering engine.
This is the code I'm working on currently:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'svgtemp.dart';
import 'dart:math';
import 'dart:ui';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: Picture(),
);
}
}
class Picture extends StatefulWidget {
const Picture({Key? key}) : super(key: key);
#override
State<Picture> createState() => _PictureState();
}
class _PictureState extends State<Picture> with SingleTickerProviderStateMixin {
late Size size_;
final Random rng = Random();
static const double pictureScalar = 1.0;
static const double backgroundScalar = 1.00;
Color color_ = Colors.black;
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat(reverse: true);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
size_ = Size(
MediaQuery.of(context).size.width, MediaQuery.of(context).size.height);
final pictureElements = <Widget>[];
for (var i = 0; i < svgdata.length; i++) {
pictureElements.add(createPicturePart(i, context));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Material(
child: Stack(
alignment: Alignment.center,
children: backgroundElements + pictureElements),
color: Colors.black,
)
],
);
}
Widget createPicturePart(int id, BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
color_ = Color.fromARGB(
0xFF, rng.nextInt(255), rng.nextInt(255), rng.nextInt(255));
});
},
child: CustomPaint(
willChange: true,
size: Size(
(pictureScalar * size_.width),
(pictureScalar * size_.width * 1.4142756349952963)
.toDouble()),
painter: RPSCustomPainter(id, color_,
CurvedAnimation(parent: _controller, curve: Curves.easeInOut))),
);
}
}
class RPSCustomPainter extends CustomPainter {
final double maxval = 0.4;
final int id_;
final Color color_;
final Animation<double> animation_;
final Path path_ = Path();
RPSCustomPainter(this.id_, this.color_, this.animation_)
: super(repaint: animation_);
#override
void paint(Canvas canvas, Size size) {
path_.moveTo(
size.width * svgdata[id_][0][0], size.height * svgdata[id_][0][1]);
for (var i = 1; i < svgdata[id_].length; i++) {
path_.cubicTo(
size.width * svgdata[id_][i][0],
size.height * svgdata[id_][i][1],
size.width * svgdata[id_][i][2],
size.height * svgdata[id_][i][3],
size.width * svgdata[id_][i][4],
size.height * svgdata[id_][i][5]);
}
path_.close();
Paint paint0Fill = Paint()..style = PaintingStyle.fill;
int colorvalue = (animation_.value * maxval * 255).toInt();
paint0Fill.color =
Color.fromARGB(0xFF, colorvalue, colorvalue, colorvalue);
canvas.drawPath(path_, paint0Fill);
}
#override
bool shouldRepaint(RPSCustomPainter oldDelegate) {
return animation_ != oldDelegate.animation_;
}
#override
bool hitTest(Offset position) {
return path_.contains(position);
}
}
I've also read the flutter performance articles but they are pretty broad and I couldn't find anything to apply here.
Maybe you have any idea?
Thanks in advance!
P.S. I can add the svgtemp.dart if you need it. (~4000 lines)
I have a strange requirement. I saw this this question on SO but i can't make it work for my case.
I have an animated container which represent my screen. On pressing the ADD icon. I'm transforming the screen like this (The column in right side image is below the homescreen) . But inside that AnimatedContainer there is a LIST(as child).
Every time I do transfromation. The list is re building itself. Is there any way i can avoid it. ?
You can imagine that the homescreen is pinned to wall with two nails in the left and right top. As I press FAB. The left top nail is pulled out and the screen hangs on right nail support. and when i again press FAB, the left top is again pinned with nail.
This is the widget I'm using
https://pub.dev/packages/matrix4_transform
Here is minimal code to see the rebuilding
import 'package:flutter/material.dart';
import 'package:matrix4_transform/matrix4_transform.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key? key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
_MyStatefulWidgetState? home;
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
DrawerManager drawerManager = DrawerManager();
callSetState() {
setState(() {});
}
#override
Widget build(BuildContext context) {
print('Rebuild');
home = this;
return AnimatedContainer(
transform: Matrix4Transform()
.translate(x: drawerManager.xOffSet, y: drawerManager.yOffSet)
.rotate(drawerManager.angle)
.matrix4,
duration: Duration(milliseconds: 500),
child: Scaffold(
body: MyList(drawerManager),
),
);
}
}
class MyList extends StatelessWidget {
final Data myData = Data();
final DrawerManager drawerManager;
MyList(this.drawerManager);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: myData.data.length+1,
itemBuilder: (context, index) {
print('Building list' + index.toString());
if(index == 4){
return GestureDetector(
child: IconButton(
icon: Icon(Icons.add),
onPressed: () {
drawerManager.callback(drawerManager.isOpened);
}),
);
}
else{return ListTile(
title: Text(myData.data[index]),
);}
},
),
);
}
}
class Data {
List<String> data = ['Hello1', 'Hello2', 'Hello3', 'Hello4'];
}
class DrawerManager {
double xOffSet = 0;
double yOffSet = 0;
double angle = 0;
bool isOpened = false;
void callback(bool isOpen) {
print('Tapped');
if (isOpen) {
xOffSet = 0;
yOffSet = 0;
angle = 0;
isOpened = false;
} else {
xOffSet = 150;
yOffSet = 80;
angle = -0.2;
isOpened = true;
}
callSetState();
}
void callSetState() {
home!.callSetState();
}
}
You can see that When you press that + icon. Screen transforms and the lists are rebuilding.
please use this class ,
https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html
Performance optimizations
If your builder function contains a subtree that does not depend on
the animation, it's more efficient to build that subtree once instead
of rebuilding it on every animation tick.
If you pass the pre-built subtree as the child parameter, the
AnimatedBuilder will pass it back to your builder function so that you
can incorporate it into your build.
You can easily achieve this using provider library.
Give it try 👍
I will recommend to use Getx if you r beginner and if
have experience in app development then I will
Recommend to use Bloc library check both of them on
Pub.
I want to implement scale and translate behavior. I write it in _TransformScaleAndTranslate().
But I got different behavior between
[Transform.translate outside Transform.rotate]
vs
[Transform.rotate outside Transform.translate]
The result shows that if I translate after scale, [Transform.translate outside Transform.rotate] can't translate follow my pointer correctly.
Is there something wrong? or anything I didn't get it?
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test1'),
),
body: const _BodyWidget(),
);
}
}
class _BodyWidget extends StatefulWidget {
const _BodyWidget({
Key key,
}) : super(key: key);
#override
__BodyWidgetState createState() => __BodyWidgetState();
}
class __BodyWidgetState extends State<_BodyWidget> {
Offset _startFocalPoint = Offset.zero;
Offset _lastOffset = Offset.zero;
Offset _currentOffset = Offset.zero;
double _lastScale = 1.0;
double _currentScale = 1.0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
_TransformScaleAndTranslate(_currentOffset, _currentScale),
],
),
);
}
void _onScaleStart(ScaleStartDetails details) {
_startFocalPoint = details.focalPoint;
_lastOffset = _currentOffset;
_lastScale = _currentScale;
}
void _onScaleUpdate(ScaleUpdateDetails details) {
if (details.scale != 1.0) {
double currentScale = _lastScale * details.scale;
if (currentScale < 0.5) {
currentScale = 0.5;
}
setState(() {
_currentScale = currentScale;
});
// The place I calculate for translate
} else if (details.scale == 1.0) {
Offset currentOffset =
_lastOffset + (details.focalPoint - _startFocalPoint) / _lastScale;
setState(() {
_currentOffset = currentOffset;
});
}
}
}
class _TransformScaleAndTranslate extends StatelessWidget {
final Offset _currentOffset;
final double _currentScale;
const _TransformScaleAndTranslate(
this._currentOffset,
this._currentScale, {
Key key,
}) : super(key: key);
//Put Transform.translate or Transform.scale outside got different behavior when translate after scaled.
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: _currentOffset,
child: Transform.scale(
scale: _currentScale,
child: Image.asset(
'assets/images/elephant.jpg',
fit: BoxFit.contain,
),
),
);
}
}
I'm not sure if how to do this.
I have a Column of AnimatedContainers. Initially all of them are 200 height. I want to implement some kind of callback, so when user tap on one item, this one becomes smaller(say 100 height) and the rest of the item in the list disappear. My AnimatedContainers are Stateful widgets
I guess I would have to use to callbacks, one for the columnn (parent) and the other to notify the children, but I don't know how to do this.
Summarised, what I have right now is
Stateless(Column(Stateful(List<AnimatedContainer>)))
If something is not clear please comment
Thanks
EDIT to add some code and more info
class SectionButton extends StatefulWidget {
double screenHeight;
Stream stream;
int index;
SectionButton(this.screenHeight, this.stream, this.index);
#override
_SectionButtonState createState() => _SectionButtonState();
}
class _SectionButtonState extends State<SectionButton> {
double height;
StreamSubscription streamSubscription;
initState() {
super.initState();
this.height = this.widget.screenHeight / n_buttons;
streamSubscription =
widget.stream.listen((_) => collapse(this.widget.index));
}
void collapse(int i) {
if (this.widget.index != i) {
setState(() {
this.height = 0;
});
} else {
setState(() {
this.height = this.widget.screenHeight / appBarFraction;
});
}
}
#override
dispose() {
super.dispose();
streamSubscription.cancel();
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
height: this.height,
duration: Duration(milliseconds: 300),
);
}
}
class HomeScreen extends StatelessWidget {
List<Widget> sections = [];
bool areCollapsed = false;
final changeNotifier = new StreamController.broadcast();
void createSections(screenHeight) {
for (var i = 0; i < buttonNames.length; i++) {
this.sections.add(GestureDetector(onTap:(){
print("Section i was tapped");
changeNotifier.sink.add(i);}, //THIS IS NOT WORKING
child: SectionButton(screenHeight, changeNotifier.stream, i),),);
}
}
#override
Widget build(BuildContext context) {
final MediaQueryData mediaQueryData = MediaQuery.of(context);
double screenHeight =
mediaQueryData.size.height - mediaQueryData.padding.vertical;
createSections(screenHeight);
return SafeArea(
child: SizedBox.expand(
child: Column(children: this.sections)
),
);
}
}
BTW what I'm trying to implement is something like this:
You have to store your height in a variable and wrap your widget with GestureDetector
GestureDetector(
onTap: () {
setState(() { _height = 100.0; });
},
child: Container(
child: Text('Click Me'),
),
)
Alternatively, you can also use AnimatedSize Widget.
Animated widget that automatically transitions its size over a given
duration whenever the given child's size changes.