Search code examples
flutterdartflutter-layoutflutter-web

How to zoom an image without the size


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?

enter image description here

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)))),
            ),
          )
        ],
      ),
    );
  }
}


Solution

  • 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.

    demo

    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),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }