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.
Related
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;
}
}
In my interface I have a row of containers like this
.
The idea is that when I pass my finger on these containers, the one under my finger gets bigger (and other changes but that's not the point).
I know how to use GestureDetector and get it bigger when I tap on the container with "onTap". But if you keep your finger down and drag it to another container nothing change. Idealy I'd like to be able to detect when the user pass his finger hover a container while touching the screen.
Appreciate if someone can advise. Thank you in advance!
You can use onVerticalDragUpdate on GestureDetector.
class DraUILien extends StatefulWidget {
const DraUILien({super.key});
#override
State<DraUILien> createState() => _DraUILienState();
}
class _DraUILienState extends State<DraUILien> {
int? activeIndex;
final double containerWidth = 30;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onVerticalDragUpdate: (details) {
activeIndex =
details.localPosition.dx ~/ (containerWidth + 16); //16 padding
setState(() {});
},
child: SizedBox(
height: 200,
child: Row(
children: List.generate(
10,
(index) => Padding(
padding: const EdgeInsets.all(8.0),
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
color: index == activeIndex ? Colors.blue : Colors.grey,
width: containerWidth,
height: index == activeIndex ? 200 : 100,
),
),
),
),
),
)),
);
}
}
Play with the logic for more customization. If you need onTap functionality try including onPanDown
I'm struggling to understand why after moving a draggable widget it gets placed into another position about 100px lower than where I'm placing it... The only thing I can think of is that it's adding the height of the Appbar and status bar ...
I thought I was doing something wrong so I decided to create the simplest example I could and it's still doing the same thing.
Just to confirm, I don't want to use drag targets, or anything like that ... I'd simply like the draggable widget to land exactly where I place the thing. However, I do need it inside of a Stack
[EDIT] It seems that removing the AppBar allows the Draggable to land exactly where you place it. However, I don't want the draggable widget to go behind the Status bar and so after adding a SafeArea I'm left with a similar problem. [/EDIT]
import 'package:flutter/material.dart';
class DraggableTest extends StatefulWidget {
static const routeName = '/draggable-test';
#override
_DraggableTestState createState() => _DraggableTestState();
}
class _DraggableTestState extends State<DraggableTest> {
Offset _dragOffset = Offset(0, 0);
Widget _dragWidget() {
return Positioned(
left: _dragOffset.dx,
top: _dragOffset.dy,
child: Draggable(
child: Container(
height: 120,
width: 90,
color: Colors.black,
),
childWhenDragging: Container(
height: 120,
width: 90,
color: Colors.grey,
),
feedback: Container(
height: 120,
width: 90,
color: Colors.red,
),
onDragEnd: (drag) {
setState(() {
_dragOffset = drag.offset;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
children: <Widget>[
_dragWidget(),
],
),
);
}
}
The main issue has to do with global position vs local position: Your Draggable widget gives global position whereas the Positioned inside your Stack takes local position. When there is no AppBar global and local positions match so the issue disappear but is still here.
So the real fix here is:
Convert your global coordinates to locale ones:
RenderBox renderBox = context.findRenderObject();
onDragEnd(renderBox.globalToLocal(drag.offset));
You now need a context. This context has to be local (the of the Draggable for example). So in the final implementation you can embed either the Stack or the Draggable in a StatelessWidget class in order to get a local context.
Here is my final implementation:
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
home: DraggableTest(),
));
}
class DraggableTest extends StatefulWidget {
static const routeName = '/draggable-test';
#override
_DraggableTestState createState() => _DraggableTestState();
}
class _DraggableTestState extends State<DraggableTest> {
Offset _dragOffset = Offset(0, 0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
children: <Widget>[
Positioned(
left: _dragOffset.dx,
top: _dragOffset.dy,
child: DragWidget(onDragEnd: onDragEnd),
),
],
),
);
}
void onDragEnd(Offset offset) {
setState(() {
_dragOffset += offset;
});
}
}
class DragWidget extends StatelessWidget {
final void Function(Offset) onDragEnd;
const DragWidget({Key key, this.onDragEnd}) : super(key: key);
#override
Widget build(BuildContext context) {
return Draggable(
child: Container(
height: 120,
width: 90,
color: Colors.black,
),
childWhenDragging: Container(
height: 120,
width: 90,
color: Colors.grey,
),
feedback: Container(
height: 120,
width: 90,
color: Colors.red,
),
onDragEnd: (drag) {
RenderBox renderBox = context.findRenderObject();
onDragEnd(renderBox.globalToLocal(drag.offset));
},
);
}
}
Note that the offset returned by renderBox.globalToLocal(drag.offset) is the offset inside the Draggable (from the start position to the end position). It is why we need to compute the final offset by setting _dragOffset += offset.
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.
I need to implement of such feature where Text or Any widget can be resized by user using a corner icons. I'm attaching a reference. Any help would be great.
I want to implement One Finger Rotate using the Icon.
I've tried implementing some similar features of few Android Sticker View library but I am not getting proper result.
I've tried the Following code.
import 'package:flutter/material.dart';
import 'package:matrix4_transform/matrix4_transform.dart';
import 'dart:math';
import 'package:render_metrics/render_metrics.dart';
class DragRotate extends StatefulWidget {
DragRotate({Key key}) : super(key: key);
#override
_DragRotateState createState() => _DragRotateState();
}
class _DragRotateState extends State<DragRotate> {
Matrix4 matrixForObject = Matrix4.identity();
double centerx, centery;
RenderParametersManager renderManager = RenderParametersManager();
GlobalKey sdsd = new GlobalKey();
#override
Widget build(BuildContext context) {
return Container(
child: Container(
child: Stack(
children: [
Center(
child: Transform(
origin: Offset(100, 100),
transform: matrixForObject,
child: Container(
key: sdsd,
height: 200,
width: 200,
color: Colors.amber,
child: Stack(
children: [
RenderMetricsObject(
manager: renderManager,
id: "w1",
child: Container(
margin: EdgeInsets.all(10),
color: Colors.amberAccent,
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onPanStart: (p) {
//// THIS IS USED TO GET THE CENTER X CORDINATE OF THE ROTATING OBJECT
centerx =
renderManager.getRenderData("w1").center.x;
//// THIS IS USED TO GET THE CENTER Y CORDINATE OF THE ROTATING OBJECT
centery =
renderManager.getRenderData("w1").center.y;
},
onPanUpdate: (s) {
double angel = atan2(
s.globalPosition.dy - centery,
s.globalPosition.dx - centerx) *
180 /
pi;
setState(() {
matrixForObject = Matrix4Transform()
.rotateByCenterDegrees(angel, Size(1, 1))
.matrix4;
});
},
child: Icon(Icons.rotate_90_degrees_ccw))),
],
),
),
),
),
],
),
),
);
}
}