如何在Flutter相机插件中实现“轻触对焦”?

4

一个非常简单的问题:如何为Flutter相机插件实现点按对焦功能?

我已经在全球范围内搜索了各种解决方案,但没有发现任何有用信息。

有人有想法吗?

2个回答

9

你需要使用相机控制器方法手动设置焦点:

controller.setFocusPoint(offset)

请查看官方API文档此处,以了解有关方法和相机功能的内容。
该方法需要一个要对焦的点。x、y坐标必须大于0且小于1,其中0是相机左上角的点,1是右下角的点。
然后您需要获取相机大小并创建比例。
这里是一个完整的示例代码,您可以复制并运行。
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

late List<CameraDescription> cameras;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  cameras = await availableCameras();
  runApp(const MaterialApp(home: CameraApp()));
}

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key}) : super(key: key);

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

class _CameraAppState extends State<CameraApp> {
  late CameraController controller;
  bool showFocusCircle = false;
  double x = 0;
  double y = 0;

  @override
  void initState() {
    super.initState();
    controller = CameraController(cameras[0], ResolutionPreset.max);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return  GestureDetector(
      onTapUp: (details) {
        _onTap(details);
      },
        child: Stack(
          children: [
            Center(
                child: CameraPreview(controller)
            ),
            if(showFocusCircle) Positioned(
                top: y-20,
                left: x-20,
                child: Container(
              height: 40,
              width: 40,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white,width: 1.5)
              ),
            ))
          ],
        )
    );
  }


  Future<void> _onTap(TapUpDetails details) async {
    if(controller.value.isInitialized) {
      showFocusCircle = true;
      x = details.localPosition.dx;
      y = details.localPosition.dy;

      double fullWidth = MediaQuery.of(context).size.width;
      double cameraHeight = fullWidth * controller.value.aspectRatio;

      double xp = x / fullWidth;
      double yp = y / cameraHeight;

      Offset point = Offset(xp,yp);
      print("point : $point");

      // Manually focus
      await controller.setFocusPoint(point);
      
      // Manually set light exposure
      //controller.setExposurePoint(point);
      
      setState(() {
        Future.delayed(const Duration(seconds: 2)).whenComplete(() {
          setState(() {
            showFocusCircle = false;
          });
        });
      });
    }
  }
}

请注意为x、y点值添加边界,以避免在轻触超出相机预览范围时导致崩溃:

if (point != null &&
      (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) {
    throw ArgumentError(
        'The values of point should be anywhere between (0,0) and (1,1).');
  }

此外,据我所知,IOS相机前置摄像头无法使用对焦点,因此只允许在后置相机镜头上触摸对焦。

不幸的是,圆圈出现在错误的位置。 - 68060

1
无论谁遇到了上述解决方案的“焦点圆”的问题,只需确保Positioned小部件是Stack小部件的直接子元素。我曾经遇到同样的问题,在几次搜索后找到了解决方案。
您可以阅读Stack小部件的文档以了解更多信息。
此外,在我使用Stack小部件之后,我在我的代码中遇到了快门按钮的位置问题。我通过简单地使用另一个Positioned小部件来容纳按钮来解决了这个问题。
这是当前最小化UI的代码。我计划添加捏合缩放和其他功能,所以它有点不完整。不管怎样,希望能对其他人有所帮助。
class _CameraPageState extends State<CameraPage> {
  late CameraController controller;
  XFile? pictureFile;

  // for tap to focus
  bool showFocusCircle = false;
  double x = 0;
  double y = 0;

  @override
  void initState() {
    super.initState();
    controller = CameraController(
      widget.cameras![0],
      ResolutionPreset.max,
    );
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

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

@override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return const SizedBox(
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }
    return GestureDetector(
      onTapUp: (details) {
        _onTap(details);
      },
      child: Stack(
        children: [
          Padding(
            padding: const EdgeInsets.all(4.0),
            child: Center(
              child: Container(
                height: 700,
                width: 700,
                margin: const EdgeInsets.only(bottom: 70),
                child: CameraPreview(controller),
              ),
            ),
          ),
          Positioned(
            left:  MediaQuery.of(context).size.width / 2 - 40,
            bottom: 40,
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: ElevatedButton(
                onPressed: () async {
                  pictureFile = await controller.takePicture();
          
                  // Preview Picture in another page
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => ImagePreview(pictureFile!),
                    ),
                  );
          
                  setState(() {});
                },
                child: const Text(' '),
              ),
            ),
          ),
          if (showFocusCircle)  
            Positioned(
              top: y - 20,
              left: x - 20,
              child: Container(
                height: 40,
                width: 40,
                decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    border: Border.all(color: Colors.white, width: 1.5)),
              ),
            )
        ],
      ),
    );
  }

  Future<void> _onTap(TapUpDetails details) async {
    if (controller.value.isInitialized) {
      showFocusCircle = true;
      x = details.localPosition.dx;
      y = details.localPosition.dy;

      print(x);
      print(y);

      double fullWidth = MediaQuery.of(context).size.width;
      double cameraHeight = fullWidth * controller.value.aspectRatio;

      double xp = x / fullWidth;
      double yp = y / cameraHeight;

      Offset point = Offset(xp, yp);
      print("point : $point");

      // Manually focus
      await controller.setFocusPoint(point);

      // Manually set light exposure
      controller.setExposurePoint(point);

      setState(() {
        Future.delayed(const Duration(seconds: 2)).whenComplete(() {
          setState(() {
            showFocusCircle = false;
          });
        });
      });
    }
  }

这就是它的样子。

https://i.imgur.com/shydRWO.jpg


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接