React native: Avoid screen going light before blur effect - react-native

I have a background image, and I want to blur it out more and more.
So far the blurring works fine:
export default class Example extends Component {
state = { blurState: 1 };
componentWillMount = () => {
this.getRandomBlurIntervals();
}
getRandomBlurIntervals = () => {
var blurState = this.state.blurState;
setInterval(() => {
blurState = blurState + Math.random();
this.setState({blurState});
}, 2000);
}
render() {
return (
<ImageBackground source={require('../images/awesomeImage.jpg')}
blurRadius = {this.state.blurState}
style={styles.backgroundImage}>
<Text >Some awesome text</Text>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
backgroundImage: {
flex: 1,
width: null,
height: null
}
});
My problem is, that when changing from blurry state to the next one, the screen goes light for a moment, like it's reloading the page. I do not need a super smooth transition from one blur state to the other, but I want to get rid of these white flashed screens.
Is there any setting that helps avoiding them?
(I don't know if react-native-blur would solve this better, but I am currently using expo, so I cannot use that one.)

Related

Getting a spinning plank screen and Error: Camera is not ready yet. Wait for 'onCameraReady' callback using expo Camera component

I'm new to web development and I'm trying to build an image recognition app using expo for testing. My code for the camera is below. On screen load, I get a black screen (not the camera) with my "capture" button. When I click on capture, I get the error:
Unhandled promise rejection: Error: Camera is not ready yet. Wait for 'onCameraReady' callback.
My code is below
import { Dimensions, Alert, StyleSheet, ActivityIndicator } from 'react-native';
// import { RNCamera } from 'react-native-camera';
import CaptureButton from './CaptureButton.js'
import { Camera } from 'expo-camera';
export default class AppCamera extends React.Component {
constructor(props){
super(props);
this.state = {
identifiedAs: '',
loading: false
}
}
takePicture = async function(){
if (this.camera) {
// Pause the camera's preview
this.camera.pausePreview();
// Set the activity indicator
this.setState((previousState, props) => ({
loading: true
}));
// Set options
const options = {
base64: true
};
// Get the base64 version of the image
const data = await this.camera.takePictureAsync(options)
// Get the identified image
this.identifyImage(data.base64);
}
}
identifyImage(imageData){
// Initialise Clarifai api
const Clarifai = require('clarifai');
const app = new Clarifai.App({
apiKey: '8d5ecc284af54894a38ba9bd7e95681b'
});
// Identify the image
app.models.predict(Clarifai.GENERAL_MODEL, {base64: imageData})
.then((response) => this.displayAnswer(response.outputs[0].data.concepts[0].name)
.catch((err) => alert(err))
);
}
displayAnswer(identifiedImage){
// Dismiss the acitivty indicator
this.setState((prevState, props) => ({
identifiedAs:identifiedImage,
loading:false
}));
// Show an alert with the answer on
Alert.alert(
this.state.identifiedAs,
'',
{ cancelable: false }
)
// Resume the preview
this.camera.resumePreview();
}
render () {
const styles = StyleSheet.create({
preview: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
},
loadingIndicator: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
});
return (
<Camera ref={ref => {this.camera = ref;}}style={styles.preview}>
<ActivityIndicator size="large" style={styles.loadingIndicator} color="#fff" animating={this.state.loading}/>
<CaptureButton buttonDisabled={this.state.loading} onClick={this.takePicture.bind(this)}/>
</Camera>
)
}
}```
Could someone kindly point me in the right direction to fix this error?
https://docs.expo.dev/versions/latest/sdk/camera/#takepictureasyncoptions
Note: Make sure to wait for the onCameraReady callback before calling this method.
So, you might resolve if you add onCameraReady props to Camera component like this document.
I'm facing issue like this, and it is not resolved now... I hope my advice works well.

How to dynamically change React Native transform with state?

I'm building a custom view that will rotate its contents based on device orientation. This app has orientation locked to portrait and I just want to rotate a single view. It fetches the current device orientation, updates the state, then renders the new component with the updated style={{transform: [{rotate: 'xxxdeg'}]}}.
I'm using react-native-orientation-locker to detect orientation changes.
The view renders correctly rotated on the first render. For example, if the screen loads while the device is rotated, it will render the view rotated. But upon changing the orientation of the device or simulator, the view does not rotate. It stays locked at the rotate value it was initialized at.
It seems like updates to the transform rotate value do not change the rotation. I've verified that new rotate values are present during the render. I've verified that orientation changes are correctly updating the state. But the view is never rotated in the UI when orientation changes. It is as if React Native isn't picking up on changes to the rotate value during a render.
I would expect that updates to the rotate value would rotate the View accordingly but that does not seem to be the case. Is there another way to accomplish this or do I have a bug in this code?
Edit: Is it required for rotate to be an Animated value?
import React, {useState, useEffect} from 'react';
import {View} from 'react-native';
import Orientation from 'react-native-orientation-locker';
const RotateView = props => {
const getRotation = newOrientation => {
switch (newOrientation) {
case 'LANDSCAPE-LEFT':
return '90deg';
case 'LANDSCAPE-RIGHT':
return '-90deg';
default:
return '0deg';
}
};
const [orientation, setOrientation] = useState(
// set orientation to the initial device orientation
Orientation.getInitialOrientation(),
);
const [rotate, setRotate] = useState(
// set rotation to the initial rotation value (xxdeg)
getRotation(Orientation.getInitialOrientation()),
);
useEffect(() => {
// Set up listeners for device orientation changes
Orientation.addDeviceOrientationListener(setOrientation);
return () => Orientation.removeDeviceOrientationListener(setOrientation);
}, []);
useEffect(() => {
// when orientation changes, update the rotation
setRotate(getRotation(orientation));
}, [orientation]);
// render the view with the current rotation value
return (
<View style={{transform: [{rotate}]}}>
{props.children}
</View>
);
};
export default RotateView;
I had this same problem, and solved it by using an Animated.View from react-native-reanimated. (Animated.View from the standard react-native package might also work, but I haven't checked). I didn't need to use an Animated value, I still just used the actual value from the state, and it worked.
If you use Animated.Value + Animated.View directly from react native you'll be fine.
Had the same issue and solved it using an Animated.Value class field (in your case I guess you'd use a useState for this one since functional + a useEffect to set the value of the Animated.Value upon changes in props.rotation), and then pass that into the Animated.View as the transform = [{ rotate: animatedRotationValue }]
Here's the class component form of this as a snippet:
interface Props {
rotation: number;
}
class SomethingThatNeedsRotation extends React.PureComponent<Props> {
rotation = new Animated.Value(0);
rotationValue = this.rotation.interpolate({
inputRange: [0, 2 * Math.PI],
outputRange: ['0deg', '360deg'],
});
render() {
this.rotation.setValue(this.props.rotation);
const transform = [{ rotate: this.rotationValue }];
return (
<Animated.View style={{ transform }} />
);
}
}
Note that in my example I also have the interpolation there since my input is in radians and I wanted it to be in degrees.
Here is my completed component that handles rotation. It will rotate its children based on device orientation while the app is locked to portrait. I'm sure this could be cleaned up some but it works for my purposes.
import React, {useState, useEffect, useRef} from 'react';
import {Animated, Easing, View, StyleSheet} from 'react-native';
import {Orientation} from '../utility/constants';
import OrientationManager from '../utility/orientation';
const OrientedView = (props) => {
const getRotation = useRef((newOrientation) => {
switch (newOrientation) {
case Orientation.LANDSCAPE_LEFT:
return 90;
case Orientation.LANDSCAPE_RIGHT:
return -90;
default:
return 0;
}
});
const {duration = 100, style} = props;
const initialized = useRef(false);
const [orientation, setOrientation] = useState();
const [rotate, setRotate] = useState();
const [containerStyle, setContainerStyle] = useState(styles.containerStyle);
// Animation kept as a ref
const rotationAnim = useRef();
// listen for orientation changes and update state
useEffect(() => {
OrientationManager.getDeviceOrientation((initialOrientation) => {
const initialRotation = getRotation.current(initialOrientation);
// default the rotation based on initial orientation
setRotate(initialRotation);
rotationAnim.current = new Animated.Value(initialRotation);
setContainerStyle([
styles.containerStyle,
{
transform: [{rotate: `${initialRotation}deg`}],
},
]);
initialized.current = true;
// set orientation and trigger the first render
setOrientation(initialOrientation);
});
OrientationManager.addDeviceOrientationListener(setOrientation);
return () =>
OrientationManager.removeDeviceOrientationListener(setOrientation);
}, []);
useEffect(() => {
if (initialized.current === true) {
const rotation = getRotation.current(orientation);
setRotate(
rotationAnim.current.interpolate({
inputRange: [-90, 0, 90],
outputRange: ['-90deg', '0deg', '90deg'],
}),
);
Animated.timing(rotationAnim.current, {
toValue: rotation,
duration: duration,
easing: Easing.ease,
useNativeDriver: true,
}).start();
}
}, [duration, orientation]);
// FIXME: This is causing unnessary animation outside of the oriented view. Disabling removes the scale animation.
// useEffect(() => {
// applyLayoutAnimation.current();
// }, [orientation]);
useEffect(() => {
if (initialized.current === true) {
setContainerStyle([
styles.containerStyle,
{
transform: [{rotate}],
},
]);
}
}, [rotate]);
if (initialized.current === false) {
return <View style={[containerStyle, style]} />;
}
return (
<Animated.View style={[containerStyle, style]}>
{props.children}
</Animated.View>
);
};
const styles = StyleSheet.create({
containerStyle: {flex: 0, justifyContent: 'center', alignItems: 'center'},
});
export default OrientedView;
This is a bug, as the rotation is supposed to change when the value of rotate updates. A workaround is to set the View's key attribute to the rotate value as well.
For example:
return (
<View
key={rotate} // <~~~ fix!
style={{transform: [{rotate}]}}
>
{props.children}
</View>
)
I found this solution here.

Toggling the animated value in order to fade in/out the view

I'm trying to toggle view's opacity with animated value, by handling the button click, but I'm not getting the desired result, except the first time button is clicked, it fades out (opacity = 0) but when I press the button again nothing happens and I can't see my view. Here's the code:
export default class App extends React.Component {
state = {
animation: new Animated.Value(1)
}
startAnimation = () => {
const { animation } = this.state
Animated.timing(animation, {
toValue: animation === 0 ? 1 : 0,
duration: 1000
}).start()
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
Does anybody know what am I doing (understanding) wrong?
Thanks!
I think it is because you don't change the state for your animated values, and this const { animation } = this.state will have always the same value, and toValue: animation === 0 ? 1 : 0, will have the same value too. I try to show you how I did this in my projects, but you have to update it for your needs.
export default class App extends React.Component {
state = {
animation: new Animated.Value(1),
isVisible: false //add a new value to check your state
}
startAnimation = () => {
const { isVisible } = this.state
Animated.timing(animation, {
toValue: isVisible === 0 ? 1 : 0,
duration: 1000
}).start(() => {
this.setState({ isVisible: !this.state.isVisible });//set the new state, so the next click will have different value
})
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
I am using this:
let val = this.state.sliderOpen ? 0.8 : 0;
Animated.timing( // Animate over time
this.state.sliderAnimation, // The animated value to drive
{
toValue: val, // Animate to opacity: 1 (opaque)
duration: 5, // Make it take a while
}
).start();
this.setState({
sliderOpen : !this.state.sliderOpen
})
Maybe try to extract the value to be changed.
Thanks to #oma I was able to get it work, here's the snack:
Toggle opacity in React Native
Besides that, I've found a nice article on this where this feature can be reused:
Animating appearance and disappearance in React Native
And here's the snack of the working example, with slight modification.
Animate opacity
This solution looks pretty well, hope you can benefit from it.
I made a node package react-native-fade-in-out that toggles a view's opacity with an animated value. You can look at the source code to see how it is accomplished, but here's a simplified version:
import React, {PureComponent} from 'react';
import {Animated} from 'react-native';
export default class FadeInOut extends PureComponent {
state = {
fadeAnim: new Animated.Value(this.props.visible ? 1 : 0),
};
componentDidUpdate(prevProps) {
if (prevProps.visible !== this.props.visible) {
Animated.timing(this.state.fadeAnim, {
toValue: prevProps.visible ? 0 : 1,
duration: 300,
}).start();
}
}
render() {
return (
<Animated.View style={{...this.props.style, opacity: this.state.fadeAnim}}>
{this.props.children}
</Animated.View>
);
}
}

Make react-native component blink at regular time interval

I am trying to make a component "blink" on my page. I was thinking about setting a visible: true state in my componentWillMount method and then put a timeout of 1s in componentDidUpdate to set state to the "opposite" of the previous state. As I see it the component lifecycle looks like this :
sets state to visible to true (componentWillMount that runs only once and is not triggering a rerender)
enters componentdidUpdate
waits 1s
hides component (setstate to visible false)
enters componentDidUpdate
waits 1s
shows component (setstate to visible true)
However my component is blinking but the intervals of hide and show are not regular, they change and dont seem to follow the 1s logic
Here's my component code :
class ResumeChronoButton extends Component {
componentWillMount(){
console.log('in componentWillMount')
this.setState({visible: true})
}
componentDidUpdate(){
console.log('in componentDidUpdate')
setTimeout(() =>this.setState({visible: !this.state.visible}), 1000)
}
// componentWillUnmount(){
// clearInterval(this.interval)
// }
render(){
const { textStyle } = styles;
if (this.state.visible){
return (
<TouchableOpacity onPress={this.props.onPress}>
<Pause style={{height: 50, width: 50}}/>
</TouchableOpacity>
);
}
else {
return (
<View style={{height: 50, width: 50}}>
</View>
)
}
}
};
How can I make my component blink at regular time interval.
The following works for me
componentDidMount = () => {
this.interval = setInterval(() => {
this.setState((state, props) => {
return {
visible: !state.visible,
};
});
}, 1000);
};
componentWillUnmount = () => {
clearInterval(this.interval);
};
and then your render can just check this.state.visible to determine if it needs to show or not.
alternatively you could change the setState to
this.setState({visible: !this.state.visible})
Most likely because you are using the state and timeouts. State is set asynchronously and, for this reason, it may take different amounts of time to change the value depending on how many resources you are using.
To achieve the effect you want I would recommendo you to use the Animation framework from React Native. Check the docs.
just use
setInterval(()=>{//setstate here},time_in_ms)

How-to make React Native lists bounce only from the bottom?

I'm using a FlatList with a consequent ListHeaderComponent as the root component of my screen. I don't want the top of it to bounce on scroll, but I want to keep the bottom bouncing for UX'n'feel purpose as the FlatList supports infinite scroll.
Any ideas how-to?
A solution would be to listen to scroll events and check whether the bounces prop inherited from ScrollView should be enabled or not. Note that I personally find this solution to be a bit of an overkill, but it works as expected.
You can find a fully working example at the following URL: https://snack.expo.io/SkL-L0knZ. You can preview it right in the browser and you can also try it on your mobile device with the Expo app.
Here is the result (forget about the lag, as this was captured in the browser):
And here is the relevant source code:
export default class App extends Component {
constructor (props) {
super(props);
this.state = { bounces: false };
this.renderHeader = this.renderHeader.bind(this);
this._onScroll = this._onScroll.bind(this);
}
_onScroll (event) {
const scrollPosition = event && event.nativeEvent && event.nativeEvent.contentOffset && event.nativeEvent.contentOffset.y;
let newBouncesValue;
if (scrollPosition < viewportHeight / 3) {
newBouncesValue = false;
} else {
newBouncesValue = true;
}
if (newBouncesValue === this.state.bounces) {
return;
}
this.setState({ bounces: newBouncesValue });
}
renderHeader () {
const { bounces } = this.state;
const style = [
styles.header,
{ backgroundColor : bounces ? 'darkseagreen' : 'firebrick'}
];
return (
<Text style={style}>{ bounces ? 'CAN BOUNCE' : "WON'T BOUNCE"}</Text>
);
}
renderItem ({item}) {
return (
<Text style={styles.row}>{item.key}</Text>
);
}
render() {
return (
<View style={styles.container}>
{ this.renderHeader() }
<FlatList
data={DUMMY_DATA}
renderItem={this.renderItem}
onScroll={this._onScroll}
bounces={this.state.bounces}
scrollEventThrottle={16}
/>
</View>
);
}
}