It's kinda tricky, but I managed to do it with a CustomPainter and a GestureDetector:
So the CustomPainter could look something like this, since I used a circular image, I make the calculations of the click based on a Circular equation to determine if the point is inside the circle.
class Painter extends CustomPainter {
const Painter({
required this.image,
required this.xPosition,
required this.yPosition,
});
final ui.Image image;
final double xPosition;
final double yPosition;
final desiredDimension = 80.0;
bool isRegion(double checkX, double checkY) {
//Check inside Square Region
// if (checkX >= xPosition - desiredDimension / 2 &&
// checkX <= xPosition + desiredDimension / 2 &&
// checkY >= yPosition - desiredDimension / 2 &&
// checkY <= yPosition + desiredDimension / 2)
//Check inside Circle Region
if ((pow(xPosition - checkX, 2) + pow(yPosition - checkY, 2)) <=
pow(desiredDimension / 2, 2)) {
return true;
}
return false;
}
@override
void paint(Canvas canvas, Size size) {
final originalWidth = image.width.toDouble();
final originalHeight = image.height.toDouble();
final scale = desiredDimension / originalHeight;
final scaledWidth = originalWidth * scale;
final rect = Rect.fromLTWH(
xPosition - scaledWidth / 2,
yPosition - desiredDimension / 2,
scaledWidth,
desiredDimension,
);
paintImage(
canvas: canvas,
image: image,
rect: rect,
fit: BoxFit.fill,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
And I modify your Widget code for the screen, using a StatefulWidget, of course:
import 'dart:math';
import 'dart:ui' as ui show Image;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Demo extends StatefulWidget {
const Demo({super.key});
@override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
late Painter _painter;
bool _isClick = false;
bool _isLoading = true;
double xPos = 100;
double yPos = 100;
ui.Image? myImage;
final bigImageHeight = 600.0;
final bigImageWidth = 400.0;
final smallImageDimension = 80.0;
@override
void initState() {
super.initState();
initialize();
}
void initialize() async {
const keyName = 'assets/images/circle.png';
final data = (await rootBundle.load(keyName));
final bytes = data.buffer.asUint8List();
myImage = await decodeImageFromList(bytes);
_painter = Painter(
image: myImage!,
xPosition: xPos,
yPosition: yPos,
);
_isLoading = false;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 5,
child: SizedBox(
height: bigImageHeight,
width: bigImageWidth,
child: _isLoading
? const Center(
child: SizedBox.square(
dimension: 12.0,
child: CircularProgressIndicator(),
),
)
: GestureDetector(
onHorizontalDragDown: (details) {
setState(
() {
if (_painter.isRegion(
details.localPosition.dx,
details.localPosition.dy,
)) {
_isClick = true;
}
},
);
},
onHorizontalDragEnd: (details) {
setState(() {
_isClick = false;
});
},
onHorizontalDragUpdate: (details) {
if (_isClick) {
if (details.localPosition.dx <
(bigImageWidth - smallImageDimension / 2) &&
details.localPosition.dx >
smallImageDimension / 2) {
xPos = details.localPosition.dx;
}
if (details.localPosition.dy <
(bigImageHeight - smallImageDimension / 2) &&
details.localPosition.dy >
smallImageDimension / 2) {
yPos = details.localPosition.dy;
}
_painter = Painter(
image: myImage!,
xPosition: xPos,
yPosition: yPos,
);
if (mounted) {
setState(() {});
}
}
},
child: MouseRegion(
cursor: _isClick
? SystemMouseCursors.grabbing
: SystemMouseCursors.grab,
child: RepaintBoundary(
key: GlobalKey(),
child: Stack(
children: [
Container(
height: bigImageHeight,
width: bigImageWidth,
decoration: _isClick
? BoxDecoration(
color: Colors.grey.shade200,
borderRadius:
BorderRadius.circular(12.0),
)
: null,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Image.asset(
'assets/images/book_1.jpeg',
semanticLabel: 'Big Image',
fit: BoxFit.fill,
),
),
),
SizedBox(
width: smallImageDimension,
height: smallImageDimension,
child: CustomPaint(
painter: _painter,
),
),
],
),
),
),
),
),
),
const Text('Text'),
],
),
);
}
}
Result:

https://raw.githubusercontent.com/AlejoCumpa/flutter-varios/ab1744473c30168544d1fa772ed73d2d90efe4fe/assets/drag-demo.gif