I have a camera with an overlaying template set up. It all works, the issue is that every time you change the slider to manage the opacity of the template it causes a full re-render of the screen. This makes the camera screen go black for a second or two then everything returns to normal. If you move the slider too much it will cause the app to crash completely.
Does anyone know how I can get the desired effect without re-rendering all the time? Here is the code:
import React, { useRef, useState, useEffect, useContext } from "react";
import { Camera } from "expo-camera";
import styled from "styled-components";
import { View, TouchableOpacity, ImageBackground, Image, Dimensions } from 'react-native';
import { Button } from "react-native-paper";
import Slider from '@react-native-community/slider';
import { Text } from "../../components/typography/text.component";
const CameraBackground = styled(View)`
background-color: #222;
`;
const ImageContainer = styled(View)`
flex: 1;
background-color: #000;
`;
const SnapButtonContainer = styled(TouchableOpacity)`
position: absolute;
z-index: 10000;
top: 50%;
margin-top: -40px;
right: 10px;
`;
const SnapButton = styled(Button)`
border: 1px #b22222 solid;
width: 60px;
height: 60px;
background-color: #8b0000;
border-radius: 50px;
`;
const OpacitySlider = styled(Slider)`
z-index: 1000;
width: 400px;
position: absolute;
top: 10px;
left: 50%;
margin-left: -200px;
`;
export const CameraScreen = ({ route, navigation }) => {
const cameraRef = useRef();
const [hasPermission, setHasPermission] = useState(null);
const [photo, setPhoto] = useState(null);
const [opacity, setOpacity] = useState(0.5);
const PlaceHolderImageContainer = styled(Image)`
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 999;
opacity: ${opacity};
flex: 1;
`;
const CameraContainer = styled(Camera)`
height: 100%;
margin: 0 auto;
width: 100%
`;
const snap = async () => {
if (cameraRef) {
let photoTaken = await cameraRef.current.takePictureAsync();
setPhoto(photoTaken);
}
};
useEffect(() => {
(async () => {
const { status } = await Camera.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
if (hasPermission === null) {
return <View />;
}
if (hasPermission === false) {
return <Text>This app does not have access to your camera</Text>
}
return (
<CameraBackground>
<SnapButtonContainer onPress={snap}>
<SnapButton />
</SnapButtonContainer>
<OpacitySlider
minimumValue={0}
maximumValue={1}
step={0.1}
minimumTrackTintColor="#FFFFFF"
maximumTrackTintColor="#000000"
value={opacity}
onValueChange={(v) => {setOpacity(v)}}
/>
<PlaceHolderImageContainer />
<CameraContainer
ref={(camera) => (cameraRef.current = camera)}
type={Camera.Constants.Type.back}
/>
</CameraBackground>
);
};
CodePudding user response:
The problem is you are updating state in the main component which triggers a re render for all others (like camera) what you should do is extract both the Image & slider into their own component like:
const ImageSlider = () => {
const [opacity, setOpacity] = useState(0.5);
const PlaceHolderImageContainer = styled(Image)`
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 999;
opacity: ${opacity};
flex: 1;
`;
return (
<>
<OpacitySlider
minimumValue={0}
maximumValue={1}
step={0.1}
minimumTrackTintColor="#FFFFFF"
maximumTrackTintColor="#000000"
value={opacity}
onValueChange={(v) => {
console.log(v);
setOpacity(v);
}}
/>
<PlaceHolderImageContainer
//source={} //add source here
/>
</>
);
};
And use it back in your own component like:
<CameraBackground>
//...
<ImageSlider />
//...
</CameraBackground>
See the example Snack: https://snack.expo.dev/SYtePkOAH