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,
),
),
),
),
],
);
}
}
Related
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(),
));
}
I'm trying to have multiple images inside a big container and be able to move them around and rotate them, as shown in the image below:
I've been playing around with CostumePainter and this is my result whilst following this guide and
Does anyone have an idea on how to do this with Images and multiple of them?
My code:
dynamic _balls;
double xPos = 100;
double yPos = 100;
bool isClick = false;
#override
Widget build(BuildContext context) {
_balls = _paint(xPosition: xPos, yPosition: yPos, ballRad: 20);
return Scaffold(
appBar: AppBar(
title: const Text("Drag and Drop"),
),
body: Center(
child: GestureDetector(
onHorizontalDragDown: (details) {
setState(() {
if (_balls.isBallRegion(
details.localPosition.dx, details.localPosition.dy)) {
isClick = true;
}
});
},
onHorizontalDragEnd: (details) {
setState(() {
isClick = false;
});
},
onHorizontalDragUpdate: (details) {
if (isClick) {
setState(() {
xPos = details.localPosition.dx;
yPos = details.localPosition.dy;
});
}
},
child: Container(
height: 300,
width: 300,
color: Colors.lightBlueAccent,
child: CustomPaint(
painter: _balls,
),
),
),
),
);
}
I followed this guide and it helped me, btw this is the blog.
Here is the code btw:
// #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(),
),
),
);
}
}
As a learning exercise, I created a basic flutter app that at least demonstrates dragging around a number of independent items and doing a couple of things like selecting, deleting, and re-ordering. The rotation function is a little buggy.
https://github.com/SpiRaiL/emoji_party_2
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 have a following code which has an image that is cropped to 150px by default. What I want is to make this cropped area remain constant while resizing the container using the resize handle.
I want the resize happen like so: (See how the cropped area is constant i.e. it doesn't move. It's as if I'm resizing an image of cropped width & height)
But in my code, it works like this:
See how the cropped area also gets cropped when resizing. I want to keep the cropped area constant(not move) while resizing. Any help would be greatly appreciated.
Here's my code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 150;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://picresize.com/images/t1rsz_pexels-photo-1108099.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
There is no problem in cropWidth. The problem is in the original image width.
You may notice the area cropWidth and width should be related in some ratio. You should calculate crop width from width (or reverse).
In your problem, the ratio seems to be a fixed number:
double _ratio = 0.5;
double _cropWidth = 150;
double _width = 300;
...
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _cropWidth / _ratio;
setState(() {});
}
...
If you want, you can change the crop ratio by modifying it
Positioned(
top: 120,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_ratio = _cropWidth / _width;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.green),
),
),
Is this what you want?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 300;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
i am also try this to do thing, may be its helpful for you
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 350;
double _width = 350;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
),
child: Image.network(
"https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
The widget cannot be touched or changed the position of the widget on the purple mark when I change the rotation of the widget.
and will be touched successfully when the rotation returns to 0 degrees or before change rotation.
working fine when not rotating
this is my code
import 'package:flutter/material.dart';
import 'dart:math' as math;
class View_Test_Design extends StatefulWidget {
#override
_View_Test_DesignState createState() => _View_Test_DesignState();
}
class _View_Test_DesignState extends State<View_Test_Design> {
double x = 100;
double y = 100;
double w = 200;
double h = 50;
double r=0; // Not Working fine when change to another number
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotate|Pos|Drag"),
),
body: Stack(
children: <Widget>[
Positioned(
left: x,
top: y,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dx;
y = y + details.delta.dy;
});
},
child: Transform.rotate(
angle: math.pi * r / 180,
child:
Container(width: w, height: h, color: Colors.red))))
],
),
);
}
}
any solution why this is not working?
This is because the Transform widget translates a widget outside the bounds of a parent widget. So you need to make the parent widget bigger.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotate|Pos|Drag"),
),
body: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
left: x,
top: y,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dx;
y = y + details.delta.dy;
});
},
child: Transform.rotate(
angle: math.pi * r / 180,
child:
Container(width: w, height: h, color: Colors.red))))
],
),
);
}
Answer is from here https://github.com/flutter/flutter/issues/27587
Had a similar problem, this 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,
),
),
),
),
],
);
}
}
Use DeferredPointer widget to detect gestures outside the bounds of a parent.