Reputation: 440
A very simple question: how to implement tap to focus functionality for the Flutter camera plugin?
I’ve searched the entire World Wide Web about solutions, but I found nothing.
Does anyone have an idea?
Upvotes: 4
Views: 8197
Reputation: 21
Whoever is facing problems with the focus circle of the above solution, just make sure that the Positioned
widget is the direct child of the Stack
widget. I had the same problem and after a few searches found the solution.
You can read the documentation for the Stack
widget to know more.
Further, after I used the Stack
widget, I faced problem with the position of the shutter button in my code. I resolved this by simply using another Positioned
widget to hold the button.
This is the my code for the current minimal UI. I am planning to add pinch to zoom and other features, so it is kind of incomplete. Anyway, I hope it helps others.
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;
});
});
});
}
}
This is how it looks.
https://i.imgur.com/shydRWO.jpg
Upvotes: 2
Reputation: 2633
You have to use the camera controller method to set the focus manually :
controller.setFocusPoint(offset)
See the official Api documention here for learn about method and camera functions.
The method need a point to focus. The x,y point have to be > 0 and < 1 where 0 is top left point of the camera and 1 the bottom right.
Then you have to retrieve your camera size and create proportions.
Here is a full sample code that you can copy and run
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;
});
});
});
}
}
}
Be aware to add bounds for the x,y point value to avoid crash if the tap is beyond the camera preview :
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).');
}
Besides, the focus point is not available for the IOS camera front as far as I know, so allow the focus tap only on back camera lens.
Upvotes: 11