I want the feedback icon to follow my cursor precisely everywhere on screen, but it's clear from this example that it doesn't:
According to my research this is however the purpose role of pointerDragAnchorStrategy.
This is my code:
import 'package:flutter/material.dart';
class DragTest extends StatelessWidget {
const DragTest({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(children: const <Widget>[
Draggable(
dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text('DRAG ME'),
),
SizedBox(height: 700),
Draggable(
dragAnchorStrategy: pointerDragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text('DRAG ME 2'),
),
]),
));
}
}
Even setting an offset manually, I have the exact same behavior:
dragAnchorStrategy:
(Draggable<Object> _, BuildContext __, Offset ___) =>
const Offset(0, 0),
For dragAnchorStrategy use the below offset:
Offset dragAnchorStrategy(
Draggable<Object> d, BuildContext context, Offset point) {
return Offset(d.feedbackOffset.dx + 50, d.feedbackOffset.dy + 50);
}
example widget:
Widget _buildItem({
required String item,
}) {
return Draggable<String>(
data: item,
dragAnchorStrategy: dragAnchorStrategy,
feedback: Icon(Icons.circle),
child: Text(item),
);
}
Full example:
import 'dart:math' as math;
import 'package:flutter/material.dart';
class ExampleDragTarget extends StatefulWidget {
const ExampleDragTarget({super.key});
#override
ExampleDragTargetState createState() => ExampleDragTargetState();
}
class ExampleDragTargetState extends State<ExampleDragTarget> {
Color _color = Colors.grey;
void _handleAccept(Color data) {
setState(() {
_color = data;
});
}
#override
Widget build(BuildContext context) {
return DragTarget<Color>(
onAccept: _handleAccept,
builder: (BuildContext context, List<Color?> data, List<dynamic> rejectedData) {
return Container(
height: 100.0,
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: data.isEmpty ? _color : Colors.grey.shade200,
border: Border.all(
width: 3.0,
color: data.isEmpty ? Colors.white : Colors.blue,
),
),
);
},
);
}
}
class Dot extends StatefulWidget {
const Dot({ super.key, this.color, this.size, this.child, this.tappable = false });
final Color? color;
final double? size;
final Widget? child;
final bool tappable;
#override
DotState createState() => DotState();
}
class DotState extends State<Dot> {
int taps = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.tappable ? () { setState(() { taps += 1; }); } : null,
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
color: widget.color,
border: Border.all(width: taps.toDouble()),
shape: BoxShape.circle,
),
child: widget.child,
),
);
}
}
class ExampleDragSource extends StatelessWidget {
const ExampleDragSource({
super.key,
this.color,
this.heavy = false,
this.under = true,
this.child,
});
final Color? color;
final bool heavy;
final bool under;
final Widget? child;
static const double kDotSize = 50.0;
static const double kHeavyMultiplier = 1.5;
static const double kFingerSize = 50.0;
#override
Widget build(BuildContext context) {
double size = kDotSize;
if (heavy) {
size *= kHeavyMultiplier;
}
final Widget contents = DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!,
textAlign: TextAlign.center,
child: Dot(
color: color,
size: size,
child: Center(child: child),
),
);
Widget feedback = Opacity(
opacity: 0.75,
child: contents,
);
Offset feedbackOffset;
DragAnchorStrategy dragAnchorStrategy;
if (!under) {
feedback = Transform(
transform: Matrix4.identity()
..translate(-size / 2.0, -(size / 2.0 + kFingerSize)),
child: feedback,
);
feedbackOffset = const Offset(0.0, -kFingerSize);
dragAnchorStrategy = (Draggable<Object> draggable,
BuildContext context, Offset position) {
return Offset.zero;
};
} else {
feedbackOffset = Offset.zero;
dragAnchorStrategy = childDragAnchorStrategy;
}
if (heavy) {
return LongPressDraggable<Color>(
data: color,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchorStrategy: dragAnchorStrategy,
child: contents,
);
} else {
return Draggable<Color>(
data: color,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchorStrategy: dragAnchorStrategy,
child: contents,
);
}
}
}
class DashOutlineCirclePainter extends CustomPainter {
const DashOutlineCirclePainter();
static const int segments = 17;
static const double deltaTheta = math.pi * 2 / segments; // radians
static const double segmentArc = deltaTheta / 2.0; // radians
static const double startOffset = 1.0; // radians
#override
void paint(Canvas canvas, Size size) {
final double radius = size.shortestSide / 2.0;
final Paint paint = Paint()
..color = const Color(0xFF000000)
..style = PaintingStyle.stroke
..strokeWidth = radius / 10.0;
final Path path = Path();
final Rect box = Offset.zero & size;
for (double theta = 0.0; theta < math.pi * 2.0; theta += deltaTheta) {
path.addArc(box, theta + startOffset, segmentArc);
}
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false;
}
class MovableBall extends StatelessWidget {
const MovableBall(this.position, this.ballPosition, this.callback, {super.key});
final int position;
final int ballPosition;
final ValueChanged<int> callback;
static final GlobalKey kBallKey = GlobalKey();
static const double kBallSize = 50.0;
#override
Widget build(BuildContext context) {
final Widget ball = DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.bodyMedium!,
textAlign: TextAlign.center,
child: Dot(
key: kBallKey,
color: Colors.blue.shade700,
size: kBallSize,
tappable: true,
child: const Center(child: Text('BALL')),
),
);
const Widget dashedBall = SizedBox(
width: kBallSize,
height: kBallSize,
child: CustomPaint(
painter: DashOutlineCirclePainter()
),
);
if (position == ballPosition) {
return Draggable<bool>(
data: true,
childWhenDragging: dashedBall,
feedback: ball,
maxSimultaneousDrags: 1,
child: ball,
);
} else {
return DragTarget<bool>(
onAccept: (bool data) { callback(position); },
builder: (BuildContext context, List<bool?> accepted, List<dynamic> rejected) {
return dashedBall;
},
);
}
}
}
class DragAndDropApp extends StatefulWidget {
const DragAndDropApp({super.key});
#override
DragAndDropAppState createState() => DragAndDropAppState();
}
class DragAndDropAppState extends State<DragAndDropApp> {
int position = 1;
void moveBall(int newPosition) {
setState(() { position = newPosition; });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Drag and Drop Flutter Demo'),
),
body: Column(
children: <Widget>[
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ExampleDragSource(
color: Colors.yellow.shade300,
child: const Text('under'),
),
ExampleDragSource(
color: Colors.green.shade300,
under: false,
heavy: true,
child: const Text('long-press above'),
),
ExampleDragSource(
color: Colors.indigo.shade300,
under: false,
child: const Text('above'),
),
],
),
),
Expanded(
child: Row(
children: const <Widget>[
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
Expanded(child: ExampleDragTarget()),
],
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MovableBall(1, position, moveBall),
MovableBall(2, position, moveBall),
MovableBall(3, position, moveBall),
],
),
),
],
),
);
}
}
use it:
void main() {
runApp(const MaterialApp(
title: 'Drag and Drop Flutter Demo',
home: DragAndDropApp(),
));
}
Related
I want to drag and drop my custom widget with gesture detector. It is showing x- direction and y- direction values but not dragging to anywhere on screen.
Here is my code:
layoutpage:
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
children: _layoutProvider.tables
.map(
(SectionTable sTable) => Positioned(
top: sTable.top,
left: sTable.left,
child: LayoutWidget(
width: sTable.width,
height: sTable.height,
type: sTable.type.index,
name: sTable.name,
left: sTable.left,
top: sTable.top,
rotate: sTable.rotate,
color: sTable.order != null
? Colors.green
: Colors.grey,
seats: sTable.seats,
),
),
)
.toList()),
),
LayoutWidget:
class LayoutWidget extends StatefulWidget {
late double width;
late double height;
late double left;
late double top;
LayoutWidget({
Key? key,
required this.width,
required this.height,
required this.left,
required this.top,
}) : super(key: key);
#override
State<StatefulWidget> createState() => _LayoutWidgetState();
}
class _LayoutWidgetState extends State<LayoutWidget> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RotationTransition(
turns: widget.type == 0
? const AlwaysStoppedAnimation(0)
: AlwaysStoppedAnimation(rotationValue / 360),
child: GestureDetector(
onPanUpdate: (details) {
widget.top = widget.top+ details.delta.dy;
widget.left = widget.left+ details.delta.dx;
setState(() {
});
},
onTap: () {
setState(() {
showMenu = !showMenu;
});
},
child: myWidget()
}
Can anyone help why i am unable to drag on screen. Thanks.
I hope you you can get Idea from this code. In this code you can drag Container anywhere in the Screen and set it. And also check this Gesture Detector Overview for Gesture Detector detail.
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class GestureDetectorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey[100],
width: double.infinity,
height: double.infinity,
child: MainContent(),
),
);
}
}
class MainContent extends StatefulWidget {
#override
_MainContentState createState() => _MainContentState();
}
class _MainContentState extends State<MainContent> {
GlobalKey key = GlobalKey();
String dragDirection = '';
String startDXPoint = '50';
String startDYPoint = '50';
String dXPoint;
String dYPoint;
String velocity;
#override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragStart: _onHorizontalDragStartHandler,
onVerticalDragStart: _onVerticalDragStartHandler,
onHorizontalDragUpdate: _onDragUpdateHandler,
onVerticalDragUpdate: _onDragUpdateHandler,
onHorizontalDragEnd: _onDragEnd,
onVerticalDragEnd: _onDragEnd,
dragStartBehavior: DragStartBehavior.start, // default
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: double.parse(this.startDXPoint),
top: double.parse(this.startDYPoint),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(100)
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text('Draggable', style: TextStyle(fontSize: 14, color: Colors.white),),
),
),
)
),
],
),
);
}
void _onHorizontalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track starting point of a vertical gesture
void _onVerticalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "VERTICAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
void _onDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "UPDATING";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track current point of a gesture
void _onHorizontalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// Track current point of a gesture
void _onVerticalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "VERTICAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// What should be done at the end of the gesture ?
void _onDragEnd(DragEndDetails details) {
double result = details.velocity.pixelsPerSecond.dx.abs().floorToDouble();
setState(() {
this.velocity = '$result';
});
}
}
You can use the Draggable widget instead. Please try this
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int acceptedData = 0;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Draggable<int>(
// Data is the value this Draggable stores.
data: 10,
feedback: Container(
color: Colors.deepOrange,
height: 100,
width: 100,
child: const Icon(Icons.directions_run),
),
childWhenDragging: Container(
height: 100.0,
width: 100.0,
color: Colors.pinkAccent,
child: const Center(
child: Text('Child When Dragging'),
),
),
child: Container(
height: 100.0,
width: 100.0,
color: Colors.lightGreenAccent,
child: const Center(
child: Text('Draggable'),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 100.0,
width: 100.0,
color: Colors.cyan,
child: Center(
child: Text('Value is updated to: $acceptedData'),
),
);
},
onAccept: (int data) {
setState(() {
acceptedData += data;
});
},
),
],
);
}
}
Hi, I was trying to build this ui, but i couldnt implement the wave effect as shown in the image.
i got some code for the wave effect but it does not fit well. I made the ui code very complex. so i made a similar ui for sharing .
///////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
class Test extends StatefulWidget {
const Test({Key key}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBg3,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buttonView(0),
buttonView(1),
buttonView(2),
buttonView(4),
],
),
),
);
}
var selectedIndex = 0;
Widget buttonView(int i) {
return Container(
margin: EdgeInsets.only(bottom: 30),
child: InkWell(
onTap: () {
selectedIndex = i;
setState(() {
});
},
child: selectedIndex == i ? WaveAnimation(child: button()) : button(),
),
);
}
Widget button() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 12,
backgroundColor: Colors.white,
child: Container(
height: 13,
width: 13,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Color(0xffD053A3), Color(0xff842990)])),
),
),
],
);
}
}
And heres the code of wave animation
class WaveAnimation extends StatefulWidget {
const WaveAnimation({
this.size = 80.0,
#required this.child,
});
final double size;
final Widget child;
#override
_WaveAnimationState createState() => _WaveAnimationState();
}
class _WaveAnimationState extends State<WaveAnimation>
with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _color = Color(0xffB05CA1);
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: CirclePainter(
_controller,
color: _color.withOpacity(0.1),
),
child: SizedBox(
width: widget.size * 2,
height: widget.size * 2,
child: _button(),
),
),
);
}
Widget _button() {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(widget.size),
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: <Color>[_color, Color.lerp(_color, Colors.black, 0.05)],
),
),
child: ScaleTransition(
scale: Tween(begin: 0.95, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: CurveWave(),
),
),
),
),
),
);
}
}
class CurveWave extends Curve {
const CurveWave();
#override
double transform(double t) {
if (t == 0 || t == 1) {
return 0.01;
}
return math.sin(t * math.pi);
}
}
class CirclePainter extends CustomPainter {
CirclePainter(
this._animation, {
#required this.color,
}) : super(repaint: _animation);
final Color color;
final Animation<double> _animation;
void circle(Canvas canvas, Rect rect, double value) {
final double opacity = (1.0 - (value / 4.0)).clamp(0.0, 0.2);
final Color _color = color.withOpacity(opacity);
final double size = rect.width / 2;
final double area = size * size;
final double radius = math.sqrt(area * value / 4);
final Paint paint = Paint()..color = _color;
canvas.drawCircle(rect.center, radius, paint);
}
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
for (int wave = 3; wave >= 0; wave--) {
circle(canvas, rect, wave + _animation.value);
}
}
#override
bool shouldRepaint(CirclePainter oldDelegate) => true;
}
To fit this effect you will use customBorder inside the inkwell.
customBorder:StadiumBorder()
I'm trying to build something similar to the slack app (see screenshot below) where the navigation drawer pushes the screen away instead of going on top.
I've been trying with the Drawer component without success. I've also looked at PageView but it seems that the children need to take 100% of the width.
Does someone have an idea of how to implement it?
EDIT
A similar result can be achieved with a Stack and AnimatedPositioned
class SlidingDrawer extends StatefulWidget {
final Widget drawer;
final Widget child;
final int swipeSensitivity;
final double drawerRatio;
final Color overlayColor;
final double overlayOpacity;
final int animationDuration;
final Curve animationCurve;
SlidingDrawer({
Key key,
#required this.drawer,
#required this.child,
this.swipeSensitivity = 25,
this.drawerRatio = 0.8,
this.overlayColor = Colors.black,
this.overlayOpacity = 0.5,
this.animationDuration = 500,
this.animationCurve = Curves.ease,
}) : super(key: key);
#override
_SlidingDrawerState createState() => _SlidingDrawerState();
}
class _SlidingDrawerState extends State<SlidingDrawer> {
bool _opened = false;
void open() {
setState(() {
_opened = true;
});
}
void close() {
setState(() {
_opened = false;
});
}
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
final drawerWidth = width * widget.drawerRatio;
return GestureDetector(
onHorizontalDragUpdate: (details) {
if (details.delta.dx > widget.swipeSensitivity) {
open();
} else if (details.delta.dx < -widget.swipeSensitivity) {
close();
}
},
child: SizedBox(
width: width,
height: height,
child: Stack(
children: [
AnimatedPositioned(
width: drawerWidth,
height: height,
left: _opened ? 0 : -drawerWidth,
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.animationCurve,
child: Container(
color: Colors.amber,
child: widget.drawer,
),
),
AnimatedPositioned(
height: height,
width: width,
left: _opened ? drawerWidth : 0,
duration: Duration(milliseconds: widget.animationDuration),
curve: widget.animationCurve,
child: Stack(
fit: StackFit.expand,
children: [
widget.child,
AnimatedSwitcher(
duration: Duration(milliseconds: widget.animationDuration),
switchInCurve: widget.animationCurve,
switchOutCurve: widget.animationCurve,
child: _opened
? GestureDetector(
onTap: () {
setState(() {
_opened = false;
});
},
child: Container(
color: widget.overlayColor.withOpacity(
widget.overlayOpacity,
),
),
)
: null,
)
],
),
),
],
),
),
);
}
}
ORIGINAL ANSWER
As pointed out by #Yadu in the comment
you could use Single child horizontal scroll view (with disabled scroll physics) with Scrollable.ensureVisible(context) to show the menu
using an horizontal scroll view is working.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _drawerOpened = false;
final drawerKey = new GlobalKey();
final mainKey = new GlobalKey();
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
child: Row(
children: [
Container(
key: drawerKey,
color: Colors.green,
width: MediaQuery.of(context).size.width * 0.8,
),
SizedBox(
key: mainKey,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Scaffold(
appBar: AppBar(
title: Text("My Page"),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _toggleDrawer,
),
),
body: Container(
color: Colors.yellow,
width: MediaQuery.of(context).size.width,
),
),
),
],
),
);
}
void _toggleDrawer() {
setState(() {
_drawerOpened = !_drawerOpened;
});
if (_drawerOpened) {
Scrollable.ensureVisible(drawerKey.currentContext);
} else {
Scrollable.ensureVisible(mainKey.currentContext);
}
}
}
I'm asking you how I can create this effect in a Flutter?
Probably you know that flutter doesn't have 3d engine. But you don't need it, you can use perspective transformations.
I've made small example for you.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 3D Cube Effect',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Scaffold(
body: Container(
child: SafeArea(
top: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('Flutter 3D Cube Effect'),
Expanded(
child: Pseudo3dSlider(),
),
],
),
),
)),
);
}
}
class Pseudo3dSlider extends StatefulWidget {
#override
_Pseudo3dSliderState createState() => _Pseudo3dSliderState();
}
class _Pseudo3dSliderState extends State<Pseudo3dSlider> {
Map<String, Offset> offsets = {
'start': Offset(70, 100),
'finish': Offset(200, 100),
'center': Offset(100, 200),
};
double originX = 0;
double x = 0;
void onDragStart(double originX) => setState(() {
this.originX = originX;
});
void onDragUpdate(double x) => setState(() {
this.x = originX - x;
});
double get turnRatio {
const step = -150.0;
var k = x / step;
k = k > 1 ? 1 : (k < 0 ? 0 : k);
return 1 - k;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: (details) => onDragUpdate(details.globalPosition.dx),
onPanUpdate: (details) => onDragUpdate(details.globalPosition.dx),
child: Slider(
children: [
_Side(
color: Colors.blueAccent,
number: 1,
),
_Side(
color: Colors.redAccent.shade200,
number: 2,
),
],
k: turnRatio,
),
);
}
}
class _Side extends StatelessWidget {
const _Side({Key key, this.color, this.number}) : super(key: key);
final Color color;
final int number;
#override
Widget build(BuildContext context) {
return Container(
width: 150,
height: 150,
color: color,
child: Center(
child: Text(
number.toString(),
style: TextStyle(fontSize: 14),
),
),
);
}
}
class Slider extends StatelessWidget {
Slider({
Key key,
#required this.children,
#required this.k,
}) : super(key: key) {
assert(children.length == 2, 'wronge nubmer of children');
}
final List<Widget> children;
final double k;
#override
Widget build(BuildContext context) {
var k1 = k;
var k2 = 1 - k;
print(k1);
print(k2);
return Row(
children: <Widget>[
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.003)
..rotateY(pi / 2 * k1),
alignment: FractionalOffset.centerRight,
child: children[0],
),
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.003)
..rotateY(pi / 2 * -k2),
alignment: FractionalOffset.centerLeft,
child: children[1],
)
],
);
}
}
I want to make a container which can be dragged around, zoom and rotate. I am able to achieve a zoom. Below is my code:
//variable declaration
double _scale = 1.0;
double _previousScale;
var yOffset = 400.0;
var xOffset = 50.0;
var rotation = 0.0;
var lastRotation = 0.0;
//build method
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Center(
child: GestureDetector(
onScaleStart: (scaleDetails) {
_previousScale = _scale;
print(' scaleStarts = ${scaleDetails.focalPoint}');
},
onScaleUpdate: (scaleUpdates){
//ScaleUpdateDetails
rotation += lastRotation - scaleUpdates.rotation;
lastRotation = scaleUpdates.rotation;
print("lastRotation = $lastRotation");
print(' scaleUpdates = ${scaleUpdates.scale} rotation = ${scaleUpdates.rotation}');
setState(() => _scale = _previousScale * scaleUpdates.scale);
},
onScaleEnd: (scaleEndDetails) {
_previousScale = null;
print(' scaleEnds = ${scaleEndDetails.velocity}');
},
child:
Transform(
transform: Matrix4.diagonal3( Vector3(_scale, _scale, _scale))..rotateZ(rotation * math.pi/180.0),
alignment: FractionalOffset.center,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.red,
),
)
,
),
),
),
);
}
Currently, there is no rotation and I can't move the container around.
use Matrix Gesture Detector package1 package, here you have the basic sample:
MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
matrix = n;
});
},
child: Transform(
transform: matrix,
child: ....
),
),
for more sample code refer to example folder that contains 6 demos
You can use RotatedBox widget (for rotation) along with InteractiveViewer widget(for zoom in and zoom out).
panEnabled property in InteractiveViewer widget used for moving the container
Scaffold(
backgroundColor: Colors.black,
body: Center(
child: RotatedBox(
quarterTurns: 1,
child: InteractiveViewer(
boundaryMargin: EdgeInsets.zero,
minScale: 1,
maxScale: 4,
child: Container(
height: 200,
width: 200,
color: Colors.blue,
),
),
),
),
),
You can also use photo_viewer instead of matrix_gesture_detector. More info in this answer: https://stackoverflow.com/a/62426232/2942294
i had same issue. the problem is solved by Matrix gesture detetctor package. but i have solved this by another case:- the whole code is here.
// #dart=2.9
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class ContainerList {
double height;
double width;
double scale;
double rotation;
double xPosition;
double yPosition;
ContainerList({
this.height,
this.rotation,
this.scale,
this.width,
this.xPosition,
this.yPosition,
});
}
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<ContainerList> list = [];
Offset _initPos;
Offset _currentPos = Offset(0, 0);
double _currentScale;
double _currentRotation;
Size screen;
#override
void initState() {
screen = Size(400, 500);
list.add(ContainerList(
height: 200.0,
width: 200.0,
rotation: 0.0,
scale: 1.0,
xPosition: 0.1,
yPosition: 0.1,
));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
height: 500.0,
color: Colors.blue.withOpacity(0.8),
width: double.infinity,
child: Stack(
children: list.map((value) {
return GestureDetector(
onScaleStart: (details) {
if (value == null) return;
_initPos = details.focalPoint;
_currentPos = Offset(value.xPosition, value.yPosition);
_currentScale = value.scale;
_currentRotation = value.rotation;
},
onScaleUpdate: (details) {
if (value == null) return;
final delta = details.focalPoint - _initPos;
final left = (delta.dx / screen.width) + _currentPos.dx;
final top = (delta.dy / screen.height) + _currentPos.dy;
setState(() {
value.xPosition = Offset(left, top).dx;
value.yPosition = Offset(left, top).dy;
value.rotation = details.rotation + _currentRotation;
value.scale = details.scale * _currentScale;
});
},
child: Stack(
children: [
Positioned(
left: value.xPosition * screen.width,
top: value.yPosition * screen.height,
child: Transform.scale(
scale: value.scale,
child: Transform.rotate(
angle: value.rotation,
child: Container(
height: value.height,
width: value.width,
child: FittedBox(
fit: BoxFit.fill,
child: Listener(
onPointerDown: (details) {
// if (_inAction) return;
// _inAction = true;
// _activeItem = val;
_initPos = details.position;
_currentPos =
Offset(value.xPosition, value.yPosition);
_currentScale = value.scale;
_currentRotation = value.rotation;
},
onPointerUp: (details) {
// _inAction = false;
},
child: Container(
height: value.height,
width: value.width,
color: Colors.red,
),
// child: Image.network(value.name),
),
),
),
),
),
)
],
),
);
}).toList(),
),
),
);
}
}
so now i can khow the rotation angle value and scale value also. in matrix gesture detector package you can not khow about this values.
Had the same problem, here is my solution:
https://stackoverflow.com/a/74165192/12098106
import 'package:flutter/material.dart';
// -------------------------------------------------------------------
// THE ITEM TO BE DRAWN
// -------------------------------------------------------------------
class DrawContainer {
Color color;
Offset offset;
double width;
double height;
double scale;
double angle;
late double _baseScaleFactor;
late double _baseAngleFactor;
DrawContainer(this.color, this.offset, this.width, this.height, this.scale,
this.angle) {
onScaleStart();
}
onScaleStart() {
_baseScaleFactor = scale;
_baseAngleFactor = angle;
}
onScaleUpdate(double scaleNew) =>
scale = (_baseScaleFactor * scaleNew).clamp(0.5, 5);
onRotateUpdate(double angleNew) => angle = _baseAngleFactor + angleNew;
}
// -------------------------------------------------------------------
// APP
// -------------------------------------------------------------------
void main() {
runApp(const MaterialApp(home: GestureTest()));
}
class GestureTest extends StatefulWidget {
const GestureTest({Key? key}) : super(key: key);
#override
// ignore: library_private_types_in_public_api
_GestureTestState createState() => _GestureTestState();
}
// -------------------------------------------------------------------
// APP STATE
// -------------------------------------------------------------------
class _GestureTestState extends State<GestureTest> {
final List<DrawContainer> containers = [
DrawContainer(Colors.red, const Offset(50, 50), 100, 100, 1.0, 0.0),
DrawContainer(Colors.yellow, const Offset(100, 100), 200, 100, 1.0, 0.0),
DrawContainer(Colors.green, const Offset(150, 150), 50, 100, 1.0, 0.0),
];
void onGestureStart(DrawContainer e) => e.onScaleStart();
onGestureUpdate(DrawContainer e, ScaleUpdateDetails d) {
e.offset = e.offset + d.focalPointDelta;
if (d.rotation != 0.0) e.onRotateUpdate(d.rotation);
if (d.scale != 1.0) e.onScaleUpdate(d.scale);
setState(() {}); // redraw
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Stack(
children: [
...containers.map((e) {
return GestureDetector(
onScaleStart: (details) {
// detect two fingers to reset internal factors
if (details.pointerCount == 2) {
onGestureStart(e);
}
},
onScaleUpdate: (details) => onGestureUpdate(e, details),
child: DrawWidget(e));
}).toList(),
],
),
),
));
}
}
// -------------------------------------------------------------------
// POSITION, ROTATE AND SCALE THE WIDGET
// -------------------------------------------------------------------
class DrawWidget extends StatelessWidget {
final DrawContainer e;
const DrawWidget(this.e, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: e.offset.dx,
top: e.offset.dy,
child: Transform.rotate(
angle: e.angle,
child: Transform.scale(
scale: e.scale,
child: Container(
height: e.width,
width: e.height,
color: e.color,
),
),
),
),
],
);
}
}