I want to zoom an image but I don't want to care about the size of the image. This widget is for wrapping any widget. The widget I transform is somewhere I don't know. That's why I add 220 to be visible. Could someone enhance my code to be adaptive for any size of widget.
class ZoomDetailPhoto extends StatefulWidget {
final Widget child;
const ZoomDetailPhoto({Key? key, required this.child}) : super(key: key);
@override
_ZoomDetailPhotoState createState() => _ZoomDetailPhotoState();
}
class _ZoomDetailPhotoState extends State<ZoomDetailPhoto> {
late Offset offset;
@override
void initState() {
super.initState();
offset = Offset.zero;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Listener(
onPointerHover: (onPointerHover) {
setState(() {
offset = onPointerHover.localPosition;
});
},
child: Stack(
alignment: Alignment.center,
children: [
widget.child,
Positioned(
left: offset.dx - 90,
top: offset.dy - 90,
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 9, color: theme.colorScheme.onBackground)),
child: Container(
width: 180,
height: 180,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: FittedBox(
fit: BoxFit.cover,
child: Transform.scale(
scale: 4,
child: Transform.translate(
offset:
Offset(-offset.dx 220, -offset.dy 220),
child: widget.child)))),
),
)
],
),
);
}
}
CodePudding user response:
To get the size of the child, you can use a GlobalKey
and assign it to the child (image or whatever widget), then in your Listener
(you can probably use MouseRegion
instead of Listener
, similar purpose, slightly easier to use) you can get the size of its child widget using GlobalKey
.
Note, however, a widget's size can only be determined after it has finished the layout process. If you don't mind lagging one frame behind, you can just do the magnifier effect with a Stack
(what you are doing currently) in the next frame. Otherwise, you can consider using an OverlayEntry
to do the magnifier instead, because overlays are built in a separate flow after normal widgets.
Note on the previous note, I personally wouldn't mind lagging one frame in this particular case, because the magnifier only shows up when user hovers it, so skipping 1 frame when the page is first loaded won't be noticeable.
Edit:
This question is pretty interesting to me, so I made a widget to do this.
Usage:
Magnifier(
magnification: 2.0,
child: Scaffold(
...
Source:
class Magnifier extends StatefulWidget {
final Widget child;
final double magnification;
const Magnifier({
Key? key,
required this.child,
this.magnification = 2.0,
}) : super(key: key);
@override
_MagnifierState createState() => _MagnifierState();
}
class _MagnifierState extends State<Magnifier> {
Offset? _offset;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
widget.child,
Positioned.fill(
child: LayoutBuilder(
builder: (_, BoxConstraints constraints) {
final childSize = constraints.biggest;
return MouseRegion(
onHover: (event) {
setState(() => _offset = event.localPosition);
},
onExit: (_) => setState(() => _offset = null),
child: _offset != null
? _buildBox(_offset!.dx, _offset!.dy, childSize)
: null,
);
},
),
)
],
);
}
Widget _buildBox(double dx, double dy, Size childSize) {
final magnifierSize = childSize.shortestSide / 2;
return Transform.translate(
offset: Offset(dx - magnifierSize / 2, dy - magnifierSize / 2),
child: Align(
alignment: Alignment.topLeft,
child: Stack(
children: [
SizedBox(
width: magnifierSize,
height: magnifierSize,
child: ClipRect(
child: Transform.scale(
scale: widget.magnification,
child: Transform.translate(
offset: Offset(
childSize.width / 2 - dx,
childSize.height / 2 - dy,
),
child: OverflowBox(
minWidth: childSize.width,
maxWidth: childSize.width,
minHeight: childSize.height,
maxHeight: childSize.height,
child: widget.child,
),
),
),
),
),
Positioned.fill(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 2),
color: Colors.green.withOpacity(0.2),
),
),
),
],
),
),
);
}
}