\n
class ZoomDetailPhoto extends StatefulWidget {\n final Widget child;\n\n const ZoomDetailPhoto({Key? key, required this.child}) : super(key: key);\n\n @override\n _ZoomDetailPhotoState createState() => _ZoomDetailPhotoState();\n}\n\nclass _ZoomDetailPhotoState extends State<ZoomDetailPhoto> {\n late Offset offset;\n\n @override\n void initState() {\n super.initState();\n offset = Offset.zero;\n }\n\n @override\n Widget build(BuildContext context) {\n final theme = Theme.of(context);\n return Listener(\n onPointerHover: (onPointerHover) {\n setState(() {\n offset = onPointerHover.localPosition;\n });\n },\n child: Stack(\n alignment: Alignment.center,\n children: [\n widget.child,\n Positioned(\n left: offset.dx - 90,\n top: offset.dy - 90,\n child: Container(\n decoration: BoxDecoration(\n border: Border.all(\n width: 9, color: theme.colorScheme.onBackground)),\n child: Container(\n width: 180,\n height: 180,\n clipBehavior: Clip.hardEdge,\n decoration: const BoxDecoration(),\n child: FittedBox(\n fit: BoxFit.cover,\n child: Transform.scale(\n scale: 4,\n child: Transform.translate(\n offset:\n Offset(-offset.dx + 220, -offset.dy + 220),\n child: widget.child)))),\n ),\n )\n ],\n ),\n );\n }\n}\n\n
\n","author":{"@type":"Person","name":"mario francois"},"upvoteCount":0,"answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"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 the user hovers it, so skipping 1 frame when the page is first loaded won't be noticeable.
\nEdit:
\nThis question is pretty interesting to me, and I just thought of another way to get a child's size in the same frame: use Positioned.fill
in a Stack
, no GlobalKey
or Overlay
needed. Using this idea, I made a widget to do this.
Usage:
\n Magnifier(\n magnification: 2.0,\n child: Scaffold(\n ...\n
\nSource:
\nclass Magnifier extends StatefulWidget {\n final Widget child;\n final double magnification;\n\n const Magnifier({\n Key? key,\n required this.child,\n this.magnification = 2.0,\n }) : super(key: key);\n\n @override\n _MagnifierState createState() => _MagnifierState();\n}\n\nclass _MagnifierState extends State<Magnifier> {\n Offset? _offset;\n\n @override\n Widget build(BuildContext context) {\n return Stack(\n alignment: Alignment.center,\n children: [\n widget.child,\n Positioned.fill(\n child: LayoutBuilder(\n builder: (_, BoxConstraints constraints) {\n final childSize = constraints.biggest;\n return MouseRegion(\n onHover: (event) {\n setState(() => _offset = event.localPosition);\n },\n onExit: (_) => setState(() => _offset = null),\n child: _offset != null\n ? _buildBox(_offset!.dx, _offset!.dy, childSize)\n : null,\n );\n },\n ),\n )\n ],\n );\n }\n\n Widget _buildBox(double dx, double dy, Size childSize) {\n final magnifierSize = childSize.shortestSide / 2;\n return Transform.translate(\n offset: Offset(dx - magnifierSize / 2, dy - magnifierSize / 2),\n child: Align(\n alignment: Alignment.topLeft,\n child: Stack(\n children: [\n SizedBox(\n width: magnifierSize,\n height: magnifierSize,\n child: ClipRect(\n child: Transform.scale(\n scale: widget.magnification,\n child: Transform.translate(\n offset: Offset(\n childSize.width / 2 - dx,\n childSize.height / 2 - dy,\n ),\n child: OverflowBox(\n minWidth: childSize.width,\n maxWidth: childSize.width,\n minHeight: childSize.height,\n maxHeight: childSize.height,\n child: widget.child,\n ),\n ),\n ),\n ),\n ),\n Positioned.fill(\n child: Container(\n decoration: BoxDecoration(\n border: Border.all(color: Colors.black, width: 2),\n color: Colors.green.withOpacity(0.2),\n ),\n ),\n ),\n ],\n ),\n ),\n );\n }\n}\n
\n","author":{"@type":"Person","name":"WSBT"},"upvoteCount":3}}}Reputation: 1371
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)))),
),
)
],
),
);
}
}
Upvotes: 0
Views: 896
Reputation: 36373
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 the 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, and I just thought of another way to get a child's size in the same frame: use Positioned.fill
in a Stack
, no GlobalKey
or Overlay
needed. Using this idea, 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),
),
),
),
],
),
),
);
}
}
Upvotes: 3