I am using expo camera and I have wrapped it inside TapGestureHandler
so I can detect tap event. Here is the code:
<TapGestureHandler onHandlerStateChange={onSingleTapEvent}>
<View>
<Camera
ref={cameraRef}
type={cameraType}
ratio={ratio}
onCameraReady={onCameraReady}
autoFocus={Camera.Constants.AutoFocus.on}
></Camera>
</View>
</TapGestureHandler>;
My onSingleTapEvent
:
const onSingleTapEvent = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("Single tap event: ", event.nativeEvent);
}
};
When I tap on the screen I see the following console output:
Single tap event: Object {
"absoluteX": 210.3333282470703,
"absoluteY": 527.3333129882812,
"handlerTag": 3,
"numberOfPointers": 1,
"oldState": 2,
"state": 4,
"x": 210.3333282470703,
"y": 446.3333435058594,
}
My question is, how do I implement tap on focus? As far as I understand I need to play with focusDepth
property of expo camera, but I don't know how to set it? Any ideas, bogs or pseudo code would be grant!
A nice way to solve this is by creating a custom useAutofocus-Hook.
import { useEffect, useState } from 'react';
import {
type GestureStateChangeEvent,
type TapGestureHandlerEventPayload,
} from 'react-native-gesture-handler';
export const useAutofocus = () => {
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
useEffect(() => {
if (isRefreshing) {
setIsRefreshing(false);
}
}, [isRefreshing]);
const onTap = (_event: GestureStateChangeEvent<TapGestureHandlerEventPayload>): void => {
setIsRefreshing(true);
};
return { isRefreshing, onTap };
};
With this hook you can conveniently react to tap gestures and use the mechanism that Louis21 described in his answer to perform a refocus on tap.
import { Camera, AutoFocus } from 'expo-camera';
import React from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { StyleSheet, View } from 'react-native';
import { useAutofocus } from '@/hooks/useAutofocus';
export default function CameraModalScreen() {
const { isRefreshing, onTap } = useAutofocus();
const tap = Gesture.Tap().onBegin(onTap);
return (
<GestureDetector gesture={tap}>
<View style={styles.container}>
<Camera style={styles.camera} autoFocus={isRefreshing ? AutoFocus.off : AutoFocus.on} />
</View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
camera: {
flex: 1,
},
contentContainer: {
flex: 1,
marginBottom: 64,
},
});
I also really like Louis solution to providing visual feedback for the tap. If you want to implement this with my solution you could expand the Hook like this
import { useEffect, useState } from 'react';
import {
type GestureStateChangeEvent,
type TapGestureHandlerEventPayload,
} from 'react-native-gesture-handler';
type FocusSquare = {
visible: boolean;
x: number;
y: number;
};
export const useAutofocus = () => {
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
const [focusSquare, setFocusSquare] = useState<FocusSquare>({ visible: false, x: 0, y: 0 });
useEffect(() => {
if (isRefreshing) {
setIsRefreshing(false);
}
}, [isRefreshing]);
const onTap = (event: GestureStateChangeEvent<TapGestureHandlerEventPayload>): void => {
const { x, y } = event;
setIsRefreshing(true);
setFocusSquare({ visible: true, x, y });
// Hide the square after 400 millliseconds.
setTimeout(() => {
setFocusSquare((prevState) => ({ ...prevState, visible: false }));
}, 400);
};
return { isRefreshing, focusSquare, onTap };
};
Inside of the component it would look really similar to Louis23's implementation
import { Camera, AutoFocus } from 'expo-camera';
import React from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { StyleSheet, View } from 'react-native';
import { useAutofocus } from '@/hooks/useAutofocus';
export default function CameraModalScreen() {
const { isRefreshing, focusSquare, onTap } = useAutofocus();
console.log(focusSquare);
const tap = Gesture.Tap().onBegin(onTap);
return (
<GestureDetector gesture={tap}>
<View style={styles.container}>
<Camera style={styles.camera} autoFocus={isRefreshing ? AutoFocus.off : AutoFocus.on} />
{focusSquare.visible && (
<View
style={[styles.focusSquare, { top: focusSquare.y - 25, left: focusSquare.x - 25 }]}
/>
)}
</View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
camera: {
flex: 1,
},
contentContainer: {
flex: 1,
marginBottom: 64,
},
focusSquare: {
position: 'absolute',
width: 50,
height: 50,
borderWidth: 2,
borderColor: 'white',
},
});