Updating a widget from a List<Widget> in flutter? - flutter

I need to know of there is any possible way to update the properties of a widget like size and colour after it is added to a List.. consider the following code..
List<Widget> tree = [];
And I'm adding the following widget when it is Dragged and dropped on the container.. To show multiple widgets i'm using a stack..
DragTarget and Stack are as follows...
DragTarget<String>(
builder: (a,b,c)=>Stack(
children: tree,
),
onAccept: (data){
tree.add(Positioned(
key: Key("$sx$sy"),
top: _y,
left: _x,
child: FlatButton(
onPressed: (){
},
child: CustomPaint(
size: Size(sx/2, sy/2),
painter: ShapePainter(shape: "circle", sx : sx/2, sy: sy/2),
child: Container(
width: sx,
height: sy,
),
),
),
)
);
}
From the Image.. I want to achieve that whenever I click a circle I should be able to update its shape and size by gestures..
NOTE
I achieved similar feature by creating a new Widget of same type and desired properties not by gestures but by filling the details in InputFields and then replacing it in following ways..
List<Widget> tree;
//Then replace it with..
tree.insert(0, Container());
OR
tree.insert(1, Container());
I don't need this to work..
I need to access the properties of the item on which I clicked and then update its shape and size with gestures.
Resource
If you need to see my complete code then use https://github.com/AbhijeetDash/designer
Feel free to contribute..

You need to create a custom stateful widget for your items and change the state whenever they are clicked.
class CustomItem extends StatefulWidget {
#override
_CustomItemState createState() => _CustomItemState();
}
class _CustomItemState extends State<CustomItem> {
var desiredChangingVariable;
#override
Widget build(BuildContext context) {
return Positioned(
key: Key("$sx$sy"),
top: _y,
left: _x,
child: FlatButton(
onPressed: (){
setState(() {
//desiredChangingVariable = newValue;
});
},
child: CustomPaint(
size: Size(sx/2, sy/2),
painter: ShapePainter(shape: "circle", sx : sx/2, sy: sy/2),
child: Container(
width: sx,
height: sy,
),
),
),
);
}
}
Additionally, make sure you won’t forget about keys when you’re dealing with populated stateful items.

Related

How to animate multiple widgets while scrolling in Flutter

I need to implement custom animation while scrolling the list of users. See an example
My current view is composed of next elements:
SingleChildScrollView contains Column with:
Row of three top elements (each of is a custom widget with basically Stack of avatar, medal and details (Column))
Row as a table header
ListView of other users.
SingleChildScrollView is wrapped with NotificationListener for ScrollNotification which is populated to provider. The scroll value is then listened in every top element to perform animation of its own.
I would like to know some general path and algorithm here to take. I tried AnimatedPositioned but as soon as it is applied on multiple elements it causes performance issues. Should I use AnimationController or some more custom things so far? Any help would be appreciated.
As pskink mentioned, using SliverPersistentHeader can be archive, This is a demo widget to illustrate how it can be done. You need to play with value. My favorite part is using .lerp , doubleLerp... to position the items.
class Appx extends StatelessWidget {
const Appx({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: CustomSliverPersistentHeaderDelegate(),
),
const SliverToBoxAdapter(
child: SizedBox(
height: 3333,
width: 200,
),
),
],
),
);
}
}
class CustomSliverPersistentHeaderDelegate
extends SliverPersistentHeaderDelegate {
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return LayoutBuilder(builder: (_, constraints) {
final t = shrinkOffset / maxExtent;
final width = constraints.maxWidth;
final itemMaxWidth = width / 4;
double xFactor = -.4;
return ColoredBox(
color: Colors.cyanAccent.withOpacity(.3),
child: Stack(
children: [
Align(
alignment:
Alignment.lerp(Alignment.center, Alignment(xFactor, -.2), t)!
..x,
child: buildRow(
color: Colors.deepPurple, itemMaxWidth: itemMaxWidth, t: t),
),
Align(
alignment: Alignment.lerp(
Alignment.centerRight, Alignment(xFactor, 0), t)!,
child:
buildRow(color: Colors.red, itemMaxWidth: itemMaxWidth, t: t),
),
Align(
alignment: Alignment.lerp(
Alignment.centerLeft, Alignment(xFactor, .2), t)!,
child: buildRow(
color: Colors.amber, itemMaxWidth: itemMaxWidth, t: t),
),
],
),
);
});
}
Container buildRow(
{required Color color, required double itemMaxWidth, required double t}) {
return Container(
width: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
height: lerpDouble(itemMaxWidth, itemMaxWidth * .3, t),
color: color,
);
}
/// you need to increase when it it not pinned
#override
double get maxExtent => 400;
#override
double get minExtent => 300;
#override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}

Dragged Positioned Should Overlap the other Positioned of the Stack

I want the first Positioned of my Stack to Overlap the Second Widget of my Stack, when it is dragged over it.
In the code example below the blue container should overlap the red container, when its dragged over it.
In my real case, I have a lot of Positioned and each one should be able to overlap the other ones when its dragged.
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: Example()));
}
class Example extends StatefulWidget {
const Example({Key? key}) : super(key: key);
#override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
late double xPosWidgetOne = 10;
late double yPosWidgetOne = 20;
late double xPosWidgetTwo = 100;
late double yPosWidgetTwo = 100;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
Positioned(
left: xPosWidgetOne,
bottom: yPosWidgetOne,
width: 50,
height: 50,
child: GestureDetector(
onPanUpdate: (details) => setState(() {
xPosWidgetOne += details.delta.dx;
yPosWidgetOne -= details.delta.dy;
}),
child: Container(
color: Colors.blue,
),
),
),
Positioned(
left: xPosWidgetTwo,
bottom: yPosWidgetTwo,
width: 50,
height: 50,
child: GestureDetector(
onPanUpdate: (details) => setState(() {
xPosWidgetTwo += details.delta.dx;
yPosWidgetTwo -= details.delta.dy;
}),
child: Container(
color: Colors.red,
),
),
),
],
));
}
}
Quote from https://api.flutter.dev/flutter/widgets/Stack-class.html
The stack paints its children in order with the first child being at
the bottom. If you want to change the order in which the children
paint, you can rebuild the stack with the children in the new order.
If you reorder the children in this way, consider giving the children
non-null keys. These keys will cause the framework to move the
underlying objects for the children to their new locations rather than
recreate them at their new location.
That means, that you could basically have all your widgets in some List property, update element orders as you wish, and pass that List property to the Stack's child property.

Resizing parent widget to fit child post 'Transform' in Flutter

I'm using Transforms in Flutter to create a scrolling carousel for selecting from various options.
This uses standard elements such as ListView.builder, which all works fine, aside from the fact that the parent widget of the Transform doesn't scale down to fit the content as seen here:
Here's the code used to generate the 'card' (there was actually a Card in there, but I've stripped it out in an attempt to get everything to scale correctly):
return Align(
child: Transform(
alignment: Alignment.center,
transform: mat,
child: Container(
height: 220,
color: color,
width: MediaQuery.of(context).size.width * 0.7,
child: Text(
offset.toString(),
style: TextStyle(color: Colors.white, fontSize: 12.0),
),
),
),
);
}
Even if I remove the 'height' parameter of the Container (so everything scales to fit the 'Text' widget), the boxes containing the Transform widgets still have the gaps around them.
Flutter doesn't seem to have any documentation to show how to re-scale the parent if the object within is transformed - anyone here knows or has any idea of a workaround?
EDIT: The widget returned from this is used within a build widget in a Stateful widget. The stack is Column > Container > ListView.builder.
If I remove the Transform, the Containers fit together as I'd like - it seems that performing a perspective transform on the Container 'shrinks' it's content (in this case, the color - check the linked screen grab), but doesn't re-scale the Container itself, which is what I'm trying to achieve.
I have a tricky solution for this: addPostFrameCallback + overlay.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
// ignore: must_be_immutable
class ChildSizeWidget extends HookWidget {
final Widget Function(BuildContext context, Widget child, Size size) builder;
final Widget child;
final GlobalKey _key = GlobalKey();
OverlayEntry _overlay;
ChildSizeWidget({ this.child, this.builder });
#override
Widget build(BuildContext context) {
final size = useState<Size>(null);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
_overlay = OverlayEntry(
builder: (context) => Opacity(
child: SingleChildScrollView(
child: Container(
child: child,
key: _key,
),
),
opacity: 0.0,
),
);
Overlay.of(context).insert(_overlay);
WidgetsBinding.instance.addPostFrameCallback((timestamp) {
size.value = _key.currentContext.size;
_overlay.remove();
});
});
return () => null;
}, [child]);
if (size == null || size.value == null) {
return child;
} else {
return builder(context, child, size.value);
}
}
}
Usage:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class HomeView extends HookWidget {
#override
Widget build(BuildContext context) {
final change = useState<bool>(false);
final normal = Container(
color: Colors.blueAccent,
height: 200.0,
width: 200.0,
);
final big = Container(
color: Colors.redAccent,
height: 300.0,
width: 200.0,
);
return Column(
children: [
Container(
alignment: Alignment.center,
child: ChildSizeWidget(
child: change.value ? big : normal,
builder: (context, child, size) => AnimatedContainer(
alignment: Alignment.center,
child: SingleChildScrollView(child: child),
duration: Duration(milliseconds: 250),
height: size.height,
),
),
color: Colors.grey,
),
FlatButton(
child: Text('Toggle child'),
onPressed: () => change.value = !change.value,
color: Colors.green,
),
],
);
}
}
I have a menu with several options, they have different height and with the help of the animations this is ok, it's working really nice for me.
Why are you using Align, as much as I can see in your code, there is no property set or used, to align anything. So try removing Align widget around Transform.
Because according to the documentation, Transform is such a widget that tries to be the same size as their children. So that would satisfy your requirement.
For more info check out this documentation: https://flutter.dev/docs/development/ui/layout/box-constraints
I hope it helps!

Move, zoom and resize Positioned widget inside Stack widget in Flutter

I would like to be able to move, rotate and zoom every element that you see in the image: 3 pictures and 1 text for example.
Those elements are Positioned widgets (the red boxes) inside a Stack widget.
I'm trying to use the package matrix_gesture_detector (https://pub.dev/packages/matrix_gesture_detector), but the problem is that I can't perform the given actions on the Positioned and I can't wrap it inside any other widget (like MatrixGestureDetector for example) that handles all actions, because "Positioned widgets must be placed directly inside Stack widgets".
If I use MatrixGestureDetector as a child of the Positioned I'm able to perform all the actions, but only inside the Positioned boundaries
How can I perform those actions directly on the Positioned? Or can I use some other widget instead of Stack/Positioned?
For me it worked pretty well.. Try something like this:
First i made a widget so that each widget can have its own Transformer Matrix
class TransformerWidget extends StatefulWidget {
final Widget child;
TransformerWidget(this.child, {Key key}) : super(key: key);
#override
_TransformerWidgetState createState() => _TransformerWidgetState();
}
class _TransformerWidgetState extends State<TransformerWidget> {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
#override
Widget build(BuildContext context) {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
return MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: widget.child,
);
},
),
);
}
}
Secondly i wrapped the widget on Stack like this:
Stack(
children: [
TransformerWidget(
Container(
color: Colors.white30,
),
),
Positioned.fill(
child: Container(
transform: notifier.value,
child: TransformerWidget(
FittedBox(
fit: BoxFit.contain,
child: Icon(
Icons.favorite,
color: Colors.deepPurple.withOpacity(0.5),
),
),
),
),
),
TransformerWidget(
Container(
decoration: FlutterLogoDecoration(),
alignment: Alignment(0, -0.5),
child: Text(
'use your two fingers to translate / rotate / scale ...',
style: Theme.of(context).textTheme.display2,
textAlign: TextAlign.center,
),
),
),
It worked great! Except that if you pinch or something touching two of the widgets, both get transformed.. Still do not know how to fix this, but it works for now! :D

Color of a widget inside a Stack is always slightly transparent

I display a custom-made bottom app bar in a Stack because of keyboard padding reasons. The custom widget is fully opaque as it should be until it's a child of a Stack in which case, the content behind it starts to be visible since the color's opacity somehow changes.
As you can see, it's only the "main" color that's transparent. Icons remain opaque.
This is the build method of my custom BottomBar widget which is then just regularly put into a Stack. I have tried using a Material and even a simple Container in place of the BottomAppBar widget but the results are the same.
#override
Widget build(BuildContext context) {
return BottomAppBar(
color: Colors.blue.withOpacity(1),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(MdiIcons.plusBoxOutline),
onPressed: () {},
),
Text('Edited 11:57'),
IconButton(
icon: Icon(MdiIcons.dotsVertical),
onPressed: () {},
),
],
),
);
}
Can you interact with the BottomAppBar ? It looks like an order problem. Try to put the BottomAppBar as last in the Stack children.
Note that BottomAppBar doesn't have a constant size, if you did not add it to Scaffold bottomNavigationBar named parameter has a size if this is not null. Below is peace of code in Scaffold dart file:
double bottomNavigationBarTop;
if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
bottomWidgetsHeight += bottomNavigationBarHeight;
bottomNavigationBarTop = math.max(0.0, bottom - bottomWidgetsHeight);
positionChild(_ScaffoldSlot.bottomNavigationBar, Offset(0.0, bottomNavigationBarTop));
}
You can even develop your own Widget without BottomAppBar but if you want things like centerDocked and things like circular notched, you will have to do more stuff (anyway you have flexibility to custom design the way you want).
Here is a simple example to do that(one way to do that):
import 'package:flutter/material.dart';
class CustomBottomBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 50),
color: Colors.greenAccent, // if you want this color under bottom bar add the margin to list view
child: ListView.builder(
itemCount: 100,
itemBuilder: (_, int index) => Text("Text $index"),
),
),
Positioned(
bottom: 0,
child: Container(
color: Colors.amber.withOpacity(.5),
width: MediaQuery.of(context).size.width,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (int index) => Text("Text $index")), // you can make these clickable by wrapping with InkWell or any gesture widget
),
),
),
],
),
);
}
}