Search code examples
fluttercamerapinchzoomgesturedetector

Flutter Camera pinch zoom


I'm trying to create a camera app in Flutter but I keep bumping into zoom issues. I managed to get the zoom code working via buttons, but when I try to replace those buttons with a GestureDetector, nothing happens.

Below is the camera page. I tried checking if my gestureDetector gets called somewhere by adding a debugLog, but this prints nothing so I think the issue is with calling the detector and not necessarily the code inside the detector


class CameraPreviewPage extends StatefulWidget {
  final CameraDescription camera;

  CameraPreviewPage({required this.camera});

  @override
  _CameraPreviewPageState createState() => _CameraPreviewPageState();
}

class _CameraPreviewPageState extends State<CameraPreviewPage> {
  late CameraController _cameraController;
  late Future<void> _initializeCameraControllerFuture;
  double zoom = 1.0;
  double _scaleFactor = 1.0;

  @override
  void initState() {
    super.initState();

    _cameraController =
        CameraController(widget.camera, ResolutionPreset.ultraHigh);

    _initializeCameraControllerFuture = _cameraController.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
       FutureBuilder(
            future: _initializeCameraControllerFuture,
            builder: (context, snapshot) {
              //The gestureDetector doesn't work
              if (snapshot.connectionState == ConnectionState.done) {
                return
                  GestureDetector(
                    behavior: HitTestBehavior.translucent,
                    onScaleStart: (details) {
                       zoom = _scaleFactor;
                    },
                    onScaleUpdate: (details) {
                       _scaleFactor = zoom * details.scale;
                       _cameraController.setZoomLevel(_scaleFactor);
                       debugPrint('Gesture updated');
                    },
                    child:CameraPreview(_cameraController));
              } else {
                return Center(child: CircularProgressIndicator());
              }
            },
          ),

        SafeArea(
            child: Scaffold(
          backgroundColor: Colors.transparent,
          floatingActionButton:
              Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
            FloatingActionButton(
              heroTag: "FABZoomIn",
              child: Icon(
                Icons.zoom_in,
                color: Colors.white,
              ),
              onPressed: () {
                if (zoom < 8) {
                  zoom = zoom + 1;
                }
                _cameraController.setZoomLevel(zoom);
              },
              backgroundColor: Colors.cyan,
            ),
            SizedBox(
              height: 10,
            ),
            FloatingActionButton(
              heroTag: "FABZoomOut",
              child: Icon(
                Icons.zoom_out,
                color: Colors.white,
              ),
              onPressed: () {
                if (zoom > 1) {
                  zoom = zoom - 1;
                }
                _cameraController.setZoomLevel(zoom);
              },
              backgroundColor: Colors.red,
            ),
            SizedBox(
              height: 10,
            ),
            FloatingActionButton(
                heroTag: "FABCamera",
                child: Icon(Icons.camera, color: Colors.white),
                backgroundColor: Colors.green,
                onPressed: () {
                  _takePicture(context);
                })
          ]),
        ))
      ],
    );
  }

  @override
  void dispose() {
    _cameraController.dispose();
    super.dispose();
  }
}

EDIT: I've also tried using onTap to see if this gives me any more insights in what could be going wrong, but onTap also doesn't get registered.


Solution

  • This is happening because the first child inside the children of a Stack is rendered first and all the other children are rendered on top of the first child.

    So, your GestureDetector will never trigger since the other children are handling the tap and obstructing the GestureDetector from receiving any hits.

    Just move your GestureDetector to the end of the children list.

    Stack(
      children: [
        SafeArea(
          ....
        ), 
        GestureDetector(
          ....
        ),
      ],
    )
    

    If your definitely need the CameraPreview at the bottom and the SafeArea to be rendered on top it, then separate your CameraPreview into another Container and have your GestureDetector as a separate widget at the end of the children list.

    Stack(
      children: [
        Container(
          child: CameraPreview(),
        ),
        SafeArea(
          ....
        ), 
        GestureDetector(
          ....
        ),
      ],
    )