How can we dynamically add drag and drop anywhere widget in flutter?
Here is what I tried to implement. Suggest changes to solve the problem or suggest the proper method to implement it.
The problem I need to solve is that the canvasItemObj.x and canvasItemObj.y variables are updating according to t the dragUpdate. But the position of the widget is not updating. (top and left parameters of the Positioned() widget)
Here is the CanvasItemProps class for holding a widget and its properties.
class CanvasItemProps {
Widget canvasItem;
double x = 0, y = 0, dx, dy;
double scale = 1;
Color color;
CanvasItemProps();
CanvasItemProps.fromItem(this.canvasItem);
}
canvasItemProps holds the list of CanvasItemProps
List<CanvasItemProps> canvasItemProps = [];
I have a flatbutton which onPressed execute the following:
var canvasItemObj = CanvasItemProps();
canvasItemProps.add(canvasItemObj);
canvasItemObj.canvasItem = Positioned(
left: canvasItemObj.x,
top: canvasItemObj.y,
child: GestureDetector(
child: Text(
addTextController.text,
textScaleFactor: 5,
),
onVerticalDragUpdate: (dragDetails) {
setState(() {
canvasItemObj.y = dragDetails.localPosition.dy;
canvasItemObj.x = dragDetails.localPosition.dx;
});
},
));
addTextController.text = "";
setState(() {});
},
In build function there is a Stack:
Stack(children:canvasItemProps.isEmpty()?
Text("No Item"):
getCanvasItem)
The following getCanvasItem function returns the list of widget to pass to the Stack widget
List<Widget> getCanvasItem() {
List<Widget> list = [];
for (int i = 0; i < canvasItemProps.length; i++) {
list.add(canvasItemProps[i].canvasItem);
}
return list;
}
On the other hand, this works fine when I am dealing with only one widget that is not dynamically added(x and y are two variables declared in the scope and are initialized to 0.)
double x=0,y=0;
widget is as follows:
Container(
margin: EdgeInsets.fromLTRB(50, 100, 90, 100),
color: Colors.yellow,
child: Stack(
children: <Widget>[
Positioned(
top: y,
left: x,
child: GestureDetector(
onVerticalDragUpdate: (v){
setState(() {
x = v.localPosition.dx;
y = v.localPosition.dy;
});
},
child:Text("Text"),
)
),
],
),
)
The issue is the Top and Left parameters are final so you cant change the values. But instead, you should reinitialize the positioned widget each time with the updated values.
Related
I want to add multiple sliders in one slider. If you're not clear about what I'm asking please refer the below image
I want these three squares to be sliding and get the values of them.
I did some searching and could not find any flutter widget or a plugin that has the support.
I tried to use a stack and use multiple Slider widgets at the same location but it is also not working. (I know it's not a good approach.)
How can I make this happen. To have multiple sliders on the same line and get the values.
Any help or ideas are very much appreciated.
Using Stack with three sliders did not work because it was being overlapped.
I have made this Slider3X of being curious. There are few things need to fix here, start and end points missing some fractional position.
Code on Gist, dart pad
class Slider3x extends StatefulWidget {
const Slider3x({
Key? key,
required this.onSliderUpdate,
this.size = const Size(5, 10),
this.min = 0,
this.max = 1.0,
this.colorX = Colors.green,
this.colorY = Colors.blue,
this.colorZ = Colors.redAccent,
}) : super(key: key);
final Function(double? x, double? y, double? z) onSliderUpdate;
///size of moveable 3x point 😅, forgot the name maybe thumbs
final Size size;
final double? min;
final double? max;
final Color colorX;
final Color colorY;
final Color colorZ;
#override
State<Slider3x> createState() => _Slider3xState();
}
class _Slider3xState extends State<Slider3x> {
/// three slider position
double? x;
double? y;
double? z;
final double tapSpacesArea = .05;
// currect active slider , help to prevent overlLAp while sliding
int activeSliderNumber = 0;
//* Update sldier
void _updateSlider(double dx, double maxWidth) {
final tapPosition = dx;
//* update logic
if (tapPosition <= 0 || tapPosition >= maxWidth) {
return;
}
//* update on UI based on slider number
if (activeSliderNumber == 0) {
setState(() {
x = tapPosition;
});
} else if (activeSliderNumber == 1) {
setState(() {
y = tapPosition;
});
} else if (activeSliderNumber == 2) {
setState(() {
z = tapPosition;
});
}
//pass value on main widget
widget.onSliderUpdate(
dp(_generateSliderValue(maxWidth: maxWidth, x: x!)),
dp(_generateSliderValue(maxWidth: maxWidth, x: y!)),
dp(_generateSliderValue(maxWidth: maxWidth, x: z!)),
);
}
//round number
double dp(double val, {int places = 2}) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
//* calculate slider value
double _generateSliderValue({
required double maxWidth,
required double x,
}) {
// x is slider original position on width:maxWidth
return (widget.max! - widget.min!) * (x / maxWidth) + widget.min!;
}
//* select ActiveSlider, fixed overLap issue
//* slider Selector logic
void _selectSlider({
required double maxWidth,
required double tapPosition,
}) {
final maxArea = maxWidth * tapSpacesArea;
if ((tapPosition - x!).abs() < maxArea) {
setState(() {
activeSliderNumber = 0;
});
} else if ((tapPosition - y!).abs() < maxArea) {
setState(() {
activeSliderNumber = 1;
});
} else if ((tapPosition - z!).abs() < maxArea) {
setState(() {
activeSliderNumber = 2;
});
}
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 50,
child: LayoutBuilder(builder: (context, constraints) {
final maxWidth = constraints.maxWidth - 10;
x = x ?? 0;
y = y ?? constraints.maxWidth / 2;
z = z ?? maxWidth;
return Stack(
alignment: Alignment.center,
children: [
Positioned(
left: x,
child: Container(
height: activeSliderNumber == 0
? widget.size.height * 1.5
: widget.size.height,
width: widget.size.width,
color: widget.colorX,
),
),
//* paint Y
Positioned(
left: y,
child: Container(
height: activeSliderNumber == 1
? widget.size.height * 1.5
: widget.size.height,
width: widget.size.width,
color: widget.colorY,
),
),
//* paint z
Positioned(
left: z,
child: Container(
height: activeSliderNumber == 2
? widget.size.height * 1.5
: widget.size.height,
width: widget.size.width,
color: widget.colorZ,
),
),
const Divider(
endIndent: 10,
),
GestureDetector(
onTapDown: (details) => _selectSlider(
maxWidth: maxWidth,
tapPosition: details.localPosition.dx),
onPanUpdate: (details) =>
_updateSlider(details.localPosition.dx, maxWidth),
),
],
);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.min.toString(),
),
Text(
widget.max.toString(),
),
],
)
],
),
);
}
}
Solved see the answers
I am using flip card package to make flip cards.
I have many cards in the same page and I want to flip them all when I press a button.
I used the example in the documentation :
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
#override
Widget build(BuildContext context) {
return FlipCard(
key: cardKey,
flipOnTouch: false,
front: Container(
child: RaisedButton(
onPressed: () => cardKey.currentState.toggleCard(),
child: Text('Toggle'),
),
),
back: Container(
child: Text('Back'),
),
);
}
but I get error Duplicate GlobalKey detected in widget tree. or Multiple widgets used the same GlobalKey
So what I can do to solve this problem ?
I solved this problem with making a map of global keys
var cardKeys = Map<int, GlobalKey<FlipCardState>>();
and in the ListView.builder in itemBuilder I added
cardKeys.putIfAbsent(index, () => GlobalKey<FlipCardState>());
GlobalKey<FlipCardState> thisCard = cardKeys[index];
and in the FlipCard I added key: thisCard
Then I make a simple for loop in the button onPressed function
RaisedButton(
onPressed: () {
for (int i = 0; i < names.length; i++) {
cardKeys[i].currentState.toggleCard();
}
},
child: Text('Toggle'),
),
Thanks to this answer here
Hello Super seniors developer. I am trying to make a post maker app like 1SStory in flutter I want to add image or text or icon in stack and then want to make it moveable , zoom in out using 2 finger pinch and rotate .
I need same as given video example
You can check here
I tried many solutions from stackoverflow these cases are below
Case1
I tried below code but it works only for make a widget moveable , I can't pinch and rotate it.
class MoveableStackItem extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MoveableStackItemState();
}
}
class _MoveableStackItemState extends State<MoveableStackItem> {
double xPosition = 0;
double yPosition = 0;
Color color;
double height = 150, width = 150;
#override
void initState() {
color = RandomColor().randomColor();
super.initState();
}
#override
Widget build(BuildContext context) {
return Positioned(
top: yPosition,
left: xPosition,
child: GestureDetector(
onPanUpdate: (tapInfo) {
setState(() {
xPosition += tapInfo.delta.dx;
yPosition += tapInfo.delta.dy;
print(xPosition);
});},
child: Container(
width: xPosition==0 ? width: xPosition,
height: yPosition ==0? height :yPosition,
color: color,),),);}}
Case2
I tried below code matrix_gesture_detector it use stack I can rotate, pinch and move but this is done in stack so when I do any operation like move zoom all widget get moved and zoom at once that it problem if you have any solution please guide me .
Stack(
children: [
MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
height: 150,
width: 150,
transform: notifier.value,
child: Container(height: 150,width: 150,color: Colors.yellow,),
),),],),);},),),],),
I am stuck at from last 5 days still doing experiments butt all in vain please guide me I shall be very thank full to you .
I want to add a button at the end of my GridView. I viewed another similar problem but the code given in the answer does not scroll. Here is the link to that answer.
My design has a similar. Here is a rough sketch.
Also just for clarification, I want the button to appear once the user has scrolled to the end of the grid view.
I am still new to flutter so your help would be much appreciated :)
The thing which you need is ScrollController class.
WHY SCROLLCONTROLLER?
It keeps track of what are we doing with scrolling, that is, scrolling, reached bottom, or top
HOW CAN WE USE IT?
You need to use it inside GridView, to get your things up and running
// Simply initialise your controller in your project
ScrollController _controller = new ScrollController();
// add listener to the controller to find out the scrolling event
_controller.addListener((){});
// pass this into your GridView.
// We we will add it to GridView. ScrollController is for every scrolling widget
// in Flutter
GridView.builder(
controller: _controller,
)
DISCLAIMER: Please do not look at the UI aspect, since we care about the scrolling event tracking and show/hide our button
I have referred to your given link only for creating the UI => Your Provided Link
Also, I have added scrolling event to identify whether we're scrolling or not, but it is commented
The project currently show the button when we reach the bottom, and hide it when we are the top
CODE
class _MyHomePageState extends State<MyHomePage> {
List<String> homeList = [];
//to check whether we have reached bottom
bool isBottom = false;
ScrollController _controller = new ScrollController();
#override
void initState() {
super.initState();
homeList = List.generate(10, (ind) => 'Item $ind').toList();
// adding controller to check whether the page is
// at the bottom
_controller.addListener((){
// reached bottom
if (_controller.offset >= _controller.position.maxScrollExtent &&
!_controller.position.outOfRange) {
setState(() => isBottom = true);
}
// IS SCROLLING
// if (_controller.offset >= _controller.position.minScrollExtent &&
// _controller.offset < _controller.position.maxScrollExtent && !_controller.position.outOfRange) {
// setState(() {
// isBottom = false;
// });
// }
// REACHED TOP
if (_controller.offset <= _controller.position.minScrollExtent &&
!_controller.position.outOfRange) {
setState(() => isBottom = false);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
GridView.builder(
shrinkWrap: true,
controller: _controller,
itemCount: homeList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisSpacing: 20),
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () => print(i),
child: Container(
margin: EdgeInsets.only(bottom: 20.0),
decoration: BoxDecoration(
color: Colors.indigo[300],
borderRadius: BorderRadius.circular(15.0)
)
)
);
}
),
// if we are bottom we show the button
// else empty container, which is nothing but
// hiding technique in Flutter
isBottom ? Positioned(
bottom: 20,
left: 18,
right: 18,
child: Container(
alignment: Alignment.center,
height: 50,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(15),
),
child: Text('Your widget at the end')
)
) : Container()
]
)
)
);
}
}
RESULT
Hi is there a way to somewhat "force" align the PageController to the left? What I want is to have it aligned to the left like the Exlore Categories without actually resizing the card
other options is to make it start in the 2nd card but I don't know how to do this one but I've tried changing initialPage to 1 to no avail
what I tried so far:
adjust the viewportFraction but this resizes the card
- Using Align widget with Alignment set to left -nothing
my code looks like this:
PageController _pageController = PageController(initialPage: 0, viewportFraction: 0.75, );
//few more codes here
//cards
return AnimatedBuilder(
animation: _pageController,
builder: (BuildContext context, Widget widget) {
double value = 1;
if (_pageController.position.haveDimensions) {
value = _pageController.page - index;
value = (1 - (value.abs() * 0.3)).clamp(0.0, 1.0);
}
return Container(
height: 200,
child: Padding(
padding: EdgeInsets.only(left: 3),
child: SizedBox(
height: 200,
width: double.infinity,
child: widget,
),
),
);
},
Set your initialPage state in initState
PageController controller;
#override
void initState() {
super.initState();
controller = PageController(initialPage: 1, viewportFraction: 0.75);
}