How to add arrow at bottom of a message box in flutter? - flutter

Please help me on how can I create this box
message box to be created in flutter

you ca use these packages to have a bubble shape for a message
bubble: ^1.2.1
flutter_chat_bubble: ^2.0.0

You can use the bubble from material design by importing
import 'package:flutter/material.dart';
or simply :-
Bubble(
margin: BubbleEdges.only(leftBottom: 10),
nip: BubbleNip.rightTop,
color: Color.fromRGBO(225, 255, 199, 1.0),
child: Text('Your Text', textAlign: TextAlign.right)
If u need more reference read this.

you can use customPainter or CustomClipper , in this case, better use clipper because we need all size from its child,
code :
import 'package:flutter/material.dart';
class MessageBox extends StatefulWidget {
const MessageBox({Key? key}) : super(key: key);
#override
_MessageBoxState createState() => _MessageBoxState();
}
class _MessageBoxState extends State<MessageBox> {
#override
Widget build(BuildContext context) {
return Material(
child: Column(
children: [
const SizedBox(height: 200,),
Container(
width: MediaQuery.of(context).size.width,
height: 100,
decoration: BoxDecoration(
color: Colors.purple,
borderRadius: BorderRadius.circular(35.0)
),
),
ClipPath(
clipper: MessageClipper(),
child: Container(
height: 20,
width: MediaQuery.of(context).size.width,
color: Colors.purple,
),
)
],
),
);
}
}
class MessageClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var firstOffset = Offset(size.width * 0.1, 0.0);
var secondPoint = Offset(size.width * 0.15, size.height );
var lastPoint = Offset(size.width * 0.2, 0.0);
var path = Path()
..moveTo(firstOffset.dx, firstOffset.dy)
..lineTo(secondPoint.dx, secondPoint.dy)
..lineTo(lastPoint.dx, lastPoint.dy)
..close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
here result :

Related

Filled circle progress bar [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 months ago.
Improve this question
Does anyone know how to do filled circle progress bar like on a picture?
You can use CustomPainter
class CirclePaint extends CustomPainter {
final double value;
CirclePaint(this.value);
#override
void paint(Canvas canvas, Size size) {
final area = Rect.fromCircle(
center: size.center(Offset.zero), radius: size.width / 2);
canvas.drawArc(
area, -pi / 2, 2 * pi * value, true, Paint()..color = Colors.amber);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
And play with this widget and change the color you like to have.
double value = .4;
final size = 200.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Slider(
value: value,
onChanged: (v) {
setState(() {
value = v;
});
},
),
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.blue,
width: 4,
),
),
child: CustomPaint(
painter: CirclePaint(value),
),
),
],
),
),
);
}
Find more about CustomPaint
With Listenable repaint as pskink mentioned on comment
class Psf extends StatelessWidget {
const Psf({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
ValueNotifier<double> value = ValueNotifier(.3);
const size = 200.0;
return Scaffold(
body: Center(
child: Column(
children: [
Slider(
value: value.value,
onChanged: (v) {
value.value = v;
},
),
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.blue,
width: 4,
),
),
child: CustomPaint(
painter: CirclePaint(value),
),
),
],
),
),
);
}
}
class CirclePaint extends CustomPainter {
final ValueNotifier<double> value;
CirclePaint(this.value) : super(repaint: value);
#override
void paint(Canvas canvas, Size size) {
final area = Rect.fromCircle(
center: size.center(Offset.zero), radius: size.width / 2);
canvas.drawArc(area, -pi / 2, 2 * pi * value.value, true,
Paint()..color = Colors.amber);
}
#override
bool shouldRepaint(covariant CirclePaint oldDelegate) =>
oldDelegate.value != value;
}
With custom decoration as #pskink included, you need to use some custom snippet for this, check them here
Container(
width: size,
height: size,
decoration: AnimatedDecoration(
listenable: value,
onPaintFrame: (canvas, bounds, double v) {
canvas.drawArc(bounds, -pi / 2, 2 * pi * v, true,
Paint()..color = Colors.amber);
},
),
),

Quarter Circle does not fit my initial circle Flutter Painter

I'm trying to make a loading icon for my loading screen using CustomPaint widget. Unfortunally, it doesn't work as expected...
My quarter circle does not fit in my primary circle, here's a screen of it :
I don't understand why it is doing this...
Here's the code:
import 'package:flutter/material.dart';
import 'package:responsive_sizer/responsive_sizer.dart';
import 'dart:math' as math;
class LoadingPage extends StatelessWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 25.h,
),
Stack(
alignment: Alignment.center,
children: [
Container(
width: 28.w,
height: 28.w,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(width: 3.w, color: Colors.white.withOpacity(0.2)),
),
),
CustomPaint(
painter: MyPainter(),
size: Size(28.w, 28.w),
),
]
),
SizedBox(
height: 6.h,
),
Text(
"LOADING",
style: Theme.of(context).textTheme.headline2,
)
],
);
}
}
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..strokeWidth = 3.w
..style = PaintingStyle.stroke
..color = Colors.blue;
double degToRad(double deg) => deg * (math.pi / 180.0);
final path = Path()
..arcTo(
Rect.fromCircle(
center: Offset(size.width/2, size.height/2),
radius: 28.w/2,
),
degToRad(90),
degToRad(90),
false);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Does anyone knows why ? :)
Thanks for your answers,
Chris

How to create a border like this in Flutter?

How to create a border like this:
You can achieve a similar clipped border using CustomClipper. Here is a simple CustomClipper I have created for you.
First Create a custom clipper.
class MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var smallLineLength = size.width / 20;
const smallLineHeight = 20;
var path = Path();
path.lineTo(0, size.height);
for (int i = 1; i <= 20; i++) {
if (i % 2 == 0) {
path.lineTo(smallLineLength * i, size.height);
} else {
path.lineTo(smallLineLength * i, size.height - smallLineHeight);
}
}
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper old) => false;
}
And wrap the created CustomClipper with ClipPath.
SizedBox(
height: 200,
width: 500,
child: ClipPath(
clipper: MyClipper(),
child: Container(
height: 200,
width: 500,
alignment: Alignment.center,
color: Colors.red,
child: const Text("abc"),
)),
),
Full Code:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
SizedBox(
height: 200,
width: 500,
child: ClipPath(
clipper: MyClipper(),
child: Container(
height: 200,
width: 500,
alignment: Alignment.center,
color: Colors.red,
child: const Text("abc"),
)),
),
]),
);
}
}
class MyClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var smallLineLength = size.width / 20;
const smallLineHeight = 20;
var path = Path();
path.lineTo(0, size.height);
for (int i = 1; i <= 20; i++) {
if (i % 2 == 0) {
path.lineTo(smallLineLength * i, size.height);
} else {
path.lineTo(smallLineLength * i, size.height - smallLineHeight);
}
}
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper old) => false;
}
You can run this code by copying/pasting in dartpad.
You can learn more about CustomClipper from here, medium article
Try below code hope its helpful to you. Used flutter_custom_clippers package here and used PointsClipper() for your expected design In this package you used more shapes see documentation in given link. add flutter_custom_clippers: ^2.0.0 dependency in your pubspec.yaml file
ClipPath(
clipper: PointsClipper(),
child: Container(
height: 100,
color: Colors.grey[300],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Total',
style: TextStyle(
color: Colors.pink,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
'QAR 130.00',
style: TextStyle(
color: Colors.pink,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
Your result screen->
import 'package:flutter/material.dart';
import 'a.dart';
void main() => runApp(VideoPlayerApp());
class VideoPlayerApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Zigzag',
home: SafeArea(
child: Scaffold(
body: ZigzagApp(),
),
),
);
}
}
class ZigzagApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: 200,
color: Colors.pinkAccent,
child: CustomPaint(
size: MediaQuery.of(context).size,
painter: MyPainter(),
),
);
}
}
//paint widget for zigzag
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MyPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.white;
paint.style = PaintingStyle.fill;
paintZigZag(canvas, paint, Offset(0, 200), Offset(400, 200), 35, 10);
}
void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end,
int zigs, double width) {
assert(zigs.isFinite);
assert(zigs > 0);
canvas.save();
canvas.translate(start.dx, start.dy);
end = end - start;
canvas.rotate(math.atan2(end.dy, end.dx));
final double length = end.distance;
final double spacing = length / (zigs * 2.0);
final Path path = Path()..moveTo(0.0, 0.0);
for (int index = 0; index < zigs; index += 1) {
final double x = (index * 2.0 + 1.0) * spacing;
final double y = width * ((index % 2.0) * 2.0 - 1.0);
path.lineTo(x, y);
}
path.lineTo(length, 0.0);
canvas.drawPath(path, paint);
canvas.restore();
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

Flutter _debugLifecycleState != _ElementLifecycle.defunct': is not true

I am showing some animated custom painter progress bar in my app it's showing some error
Error: The following assertion was thrown while notifying listeners for AnimationController:
'package:flutter/src/widgets/framework.dart': Failed assertion: line 4263 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true.
I have a simple homePage in which I have a navigation bar. I am simply showing the container when navigation change like this
SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/sidebg.png"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
pageIndex == 0 ? DashboardScreen() : Container(),
pageIndex == 1 ? MapScreen() : Container(),
pageIndex == 3 ? ServiceCenter() : Container(),
pageIndex == 4 ? ProfileScreen() : Container(),
],
)),
),
Issue is as you can see pages changes when index are changing but when i change the page its showing an error as i mention above in continuesly loop not stopping.
If I remove this progress indicator all is working fine.
This is the progress indicator screen
import 'dart:math' as math;
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
import 'package:sleek_circular_slider/sleek_circular_slider.dart';
import 'package:liquid_progress_indicator/liquid_progress_indicator.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:smooth_star_rating/smooth_star_rating.dart';
class DashboardScreen extends StatefulWidget {
#override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
Circular_arc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [
0.0,
0.5,
1.0,
],
);
class Circular_arc extends StatefulWidget {
const Circular_arc({
Key key,
}) : super(key: key);
#override
_Circular_arcState createState() => _Circular_arcState();
}
class _Circular_arcState extends State<Circular_arc>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController animController;
#override
void initState() {
// TODO: implement initState
super.initState();
animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
Image.asset(
'images/star-icon-fill#3x.png',
height: height * 0.045,
),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
Error is in the continuous loop it's not stopping. And also its starts when I change the page index (mean when I change the navigation from home).
In your _CircularArcState, please...
Call animController.forward(); after animController.repeat(max: 1);
To save the state of CircularArc, add mixin AutomaticKeepAliveClientMixin in _CircularArcState. Then override wantKeepAlive and return true. Also, call super.build(context); inside build(...).
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView(
scrollDirection: Axis.vertical,
children: [
DashboardScreen(),
Container(color: Colors.orange),
Container(color: Colors.blue),
Container(color: Colors.green),
],
),
),
);
}
}
class DashboardScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
return Container(
child: Column(
children: [
SizedBox(
height: height * 0.05,
),
CircularArc(),
],
),
);
}
}
final Gradient gradient = new LinearGradient(
colors: <Color>[
Colors.greenAccent.withOpacity(1.0),
Colors.yellowAccent.withOpacity(1.0),
Colors.redAccent.withOpacity(1.0),
],
stops: [0.0, 0.5, 1.0],
);
class CircularArc extends StatefulWidget {
const CircularArc({
Key key,
}) : super(key: key);
#override
_CircularArcState createState() => _CircularArcState();
}
class _CircularArcState extends State<CircularArc>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
double width;
double height;
Animation<double> animation;
#override
void initState() {
super.initState();
final AnimationController animController =
AnimationController(duration: Duration(seconds: 3), vsync: this);
final curvedAnimation =
CurvedAnimation(parent: animController, curve: Curves.easeInOutCubic);
animation = Tween<double>(begin: 0.0, end: 2).animate(curvedAnimation)
..addListener(() {
setState(() {});
});
animController.repeat(max: 1);
animController.forward();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
if (width == null && height == null) {
width = MediaQuery.of(context).size.width;
height = MediaQuery.of(context).size.height;
}
return Container(
child: Stack(
children: [
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(null, Colors.black54, true),
),
CustomPaint(
size: Size(300, 300),
painter: ProgressArc(animation.value, Colors.redAccent, false),
),
Positioned(
top: height * 0.07,
left: width * 0.2,
child: Column(
children: [
// Image.asset(
// 'images/star-icon-fill#3x.png',
// height: height * 0.045,
// ),
RichText(
text: new TextSpan(
// Note: Styles for TextSpans must be explicitly defined.
// Child text spans will inherit styles from parent
style: new TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: <TextSpan>[
new TextSpan(
text: '4.6',
style: new TextStyle(
fontSize: 40, fontFamily: 'UbuntuRegular')),
new TextSpan(
text: ' /5',
style: TextStyle(
fontSize: 25,
color: Colors.grey[400],
fontFamily: 'UbuntuRegular')),
],
),
),
Text(
'FIFTEEN DAYS SCORE',
style: TextStyle(
color: Colors.grey[400], fontFamily: 'UbuntuMedium'),
)
],
),
)
],
),
);
}
}
class ProgressArc extends CustomPainter {
bool isBackground;
double arc;
Color progressColor;
ProgressArc(this.arc, this.progressColor, this.isBackground);
#override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTRB(0, 0, 300, 300);
final startAngle = -math.pi;
final sweepAngle = arc != null ? arc : math.pi;
final userCenter = false;
final paint = Paint()
..strokeCap = StrokeCap.round
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = 10;
if (!isBackground) {
paint.shader = gradient.createShader(rect);
}
canvas.drawArc(rect, startAngle, sweepAngle, userCenter, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
first dispose the controller and then use super.dispose()
void dispose() { controller.dispose(); super.dispose(); }

Flutter: How to set boundaries for a Draggable widget?

I'm trying to create a drag and drop game. I would like to make sure that the Draggable widgets don't get out of the screen when they are dragged around.
I couldn't find an answer to this specific question. Someone asked something similar about constraining draggable area Constraining Draggable area but the answer doesn't actually make use of Draggable.
To start with I tried to implement a limit on the left-hand side.
I tried to use a Listener with onPointerMove. I've associated this event with a limitBoundaries method to detect when the Draggable exits from the left side of the screen. This part is working as it does print in the console the Offset value when the Draggable is going out (position.dx < 0). I also associated a setState to this method to set the position of the draggable to Offset(0.0, position.dy) but this doesn't work.
Could anybody help me with this?
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Test',
home: GamePlay(),
);
}
}
class GamePlay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Row(
children: [
Container(
width: 360,
height: 400,
decoration: BoxDecoration(
color: Colors.lightGreen,
border: Border.all(
color: Colors.green,
width: 2.0,
),
),
),
Container(
width: 190,
height: 400,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.purple,
width: 2.0,
),
),
),
],
),
DragObject(
key: GlobalKey(),
initPos: Offset(365, 0.0),
id: 'Item 1',
itmColor: Colors.orange),
DragObject(
key: GlobalKey(),
initPos: Offset(450, 0.0),
id: 'Item 2',
itmColor: Colors.pink,
),
],
),
);
}
}
class DragObject extends StatefulWidget {
final String id;
final Offset initPos;
final Color itmColor;
DragObject({Key key, this.id, this.initPos, this.itmColor}) : super(key: key);
#override
_DragObjectState createState() => _DragObjectState();
}
class _DragObjectState extends State<DragObject> {
GlobalKey _key;
Offset position;
Offset posOffset = Offset(0.0, 0.0);
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
_key = widget.key;
position = widget.initPos;
super.initState();
}
void _getRenderOffsets() {
final RenderBox renderBoxWidget = _key.currentContext.findRenderObject();
final offset = renderBoxWidget.localToGlobal(Offset.zero);
posOffset = offset - position;
}
void _afterLayout(_) {
_getRenderOffsets();
}
void limitBoundaries(PointerEvent details) {
if (details.position.dx < 0) {
print(details.position);
setState(() {
position = Offset(0.0, position.dy);
});
}
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Listener(
onPointerMove: limitBoundaries,
child: Draggable(
child: Container(
width: 80,
height: 80,
color: widget.itmColor,
),
feedback: Container(
width: 82,
height: 82,
color: widget.itmColor,
),
childWhenDragging: Container(),
onDragEnd: (drag) {
setState(() {
position = drag.offset - posOffset;
});
},
),
),
);
}
}
Try this. I tweaked this from: Constraining Draggable area .
ValueNotifier<List<double>> posValueListener = ValueNotifier([0.0, 0.0]);
ValueChanged<List<double>> posValueChanged;
double _horizontalPos = 0.0;
double _verticalPos = 0.0;
#override
void initState() {
super.initState();
posValueListener.addListener(() {
if (posValueChanged != null) {
posValueChanged(posValueListener.value);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildDraggable(),
]));
}
_buildDraggable() {
return SafeArea(
child: Container(
margin: EdgeInsets.only(bottom: 100),
color: Colors.green,
child: Builder(
builder: (context) {
final handle = GestureDetector(
onPanUpdate: (details) {
_verticalPos =
(_verticalPos + details.delta.dy / (context.size.height))
.clamp(.0, 1.0);
_horizontalPos =
(_horizontalPos + details.delta.dx / (context.size.width))
.clamp(.0, 1.0);
posValueListener.value = [_horizontalPos, _verticalPos];
},
child: Container(
child: Container(
margin: EdgeInsets.all(12),
width: 110.0,
height: 170.0,
child: Container(
color: Colors.black87,
),
decoration: BoxDecoration(color: Colors.black54),
),
));
return ValueListenableBuilder<List<double>>(
valueListenable: posValueListener,
builder:
(BuildContext context, List<double> value, Widget child) {
return Align(
alignment: Alignment(value[0] * 2 - 1, value[1] * 2 - 1),
child: handle,
);
},
);
},
),
),
);
}
I've found a workaround for this issue. It's not exactly the output I was looking for but I thought this could be useful to somebody else.
Instead of trying to control the drag object during dragging, I just let it go outside of my screen and I placed it back to its original position in case it goes outside of the screen.
Just a quick note if someone tries my code, I forgot to mention that I'm trying to develop a game for the web. The output on a mobile device might be a little bit odd!
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Test',
home: GamePlay(),
);
}
}
class GamePlay extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Row(
children: [
Container(
width: 360,
height: 400,
decoration: BoxDecoration(
color: Colors.lightGreen,
border: Border.all(
color: Colors.green,
width: 2.0,
),
),
),
Container(
width: 190,
height: 400,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.purple,
width: 2.0,
),
),
),
],
),
DragObject(
key: GlobalKey(),
initPos: Offset(365, 0.0),
id: 'Item 1',
itmColor: Colors.orange),
DragObject(
key: GlobalKey(),
initPos: Offset(450, 0.0),
id: 'Item 2',
itmColor: Colors.pink,
),
],
),
);
}
}
class DragObject extends StatefulWidget {
final String id;
final Offset initPos;
final Color itmColor;
DragObject({Key key, this.id, this.initPos, this.itmColor}) : super(key: key);
#override
_DragObjectState createState() => _DragObjectState();
}
class _DragObjectState extends State<DragObject> {
GlobalKey _key;
Offset position;
Offset posOffset = Offset(0.0, 0.0);
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
_key = widget.key;
position = widget.initPos;
super.initState();
}
void _getRenderOffsets() {
final RenderBox renderBoxWidget = _key.currentContext.findRenderObject();
final offset = renderBoxWidget.localToGlobal(Offset.zero);
posOffset = offset - position;
}
void _afterLayout(_) {
_getRenderOffsets();
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Listener(
child: Draggable(
child: Container(
width: 80,
height: 80,
color: widget.itmColor,
),
feedback: Container(
width: 82,
height: 82,
color: widget.itmColor,
),
childWhenDragging: Container(),
onDragEnd: (drag) {
setState(() {
if (drag.offset.dx > 0) {
position = drag.offset - posOffset;
} else {
position = widget.initPos;
}
});
},
),
),
);
}
}
I'm still interested if someone can find a proper solution to the initial issue :-)
you could use the property onDragEnd: of the widget Draggable and before setting the new position compare it with the height or width of your device using MediaQuery and update only if you didn't pass the limits of your screen, else set the new position to the initial one.
Example bellow :
Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
maxSimultaneousDrags: 1,
childWhenDragging:
Opacity(opacity: .2, child: rangeEvent(context)),
feedback: rangeEvent(context),
axis: Axis.vertical,
affinity: Axis.vertical,
onDragEnd: (details) => updatePosition(details.offset),
child: Transform.scale(
scale: scale,
child: rangeEvent(context),
),
),
)
In the method updatePosition, you verify the new position before updating:
void updatePosition(Offset newPosition) => setState(() {
if (newPosition.dy > 10 &&
newPosition.dy < MediaQuery.of(context).size.height * 0.9) {
position = newPosition;
} else {
position = const Offset(0, 0);// initial possition
}
});