framedrop on real device, not on simulator (IOS) - React Native Reanimated - react-native

I have the following code to create a 'dynamic' textinput. A label is placed above the textinput. When the textinput is focused, the label moves above the textinput and the fontsize and color changes. On blur of the textinput, the label moves back (if there is no input in the textinput).
This code contains react-native-reanimated code, should animations should work on the UI thread, not the JS thread.
When I test this code on an IOS simulator (tested iPhone 7 -> iPhone 11 Pro) and I focus on a textinput so the animation runs, the JS thread drops about 3-6 frames (54-57 fps), which is OK I guess. On a real device (iPhone 7), the JS thread drops about 20-30 frames (and sometimes even more). When I start typing in the textinput, I get a slow callback (I check the input of the textinput while typing). On the simulator, the callback is immediately. On the real device, it sometimes takes up to 2 seconds before the input is checked.
import React, { useState, useEffect } from 'react';
import { View, TextInput } from 'react-native';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { interpolateColor, useTimingTransition } from 'react-native-redash';
import Colors from '../../constants/Colors';
const { interpolate } = Animated;
const AuthenticationInput = React.forwardRef((props, ref) => {
const {
ref,
label,
onChangeText,
secureTextEntry,
returnKeyType,
icon
} = props;
const [value, setValue] = useState('');
const [trans, setTrans] = useState(0);
const transition = useTimingTransition(trans, {
duration: 250,
easing: Easing.inOut(Easing.ease)
});
// move the label in the x direction
const moveX = interpolate(transition, {
inputRange: [0, 1],
outputRange: [12.5, 0],
extrapolate: Extrapolate.CLAMP
});
// move the label in the y direction
const moveY = interpolate(transition, {
inputRange: [0, 1],
outputRange: [12.5, -20],
extrapolate: Extrapolate.CLAMP
});
// change the font size of the label
const fontSize = interpolate(transition, {
inputRange: [0, 1],
outputRange: [15, 12.5],
extrapolate: Extrapolate.CLAMP
});
// change the color of the label
const color = interpolateColor(transition, {
inputRange: [0, 1],
outputRange: ['#aaa', '#000']
});
// pass the input of the textinput to the
// onChangeText function passed as a prop
useEffect(() => {
onChangeText(value)
}, [value]);
return (
<View style={{
marginHorizontal: 25,
marginTop: 25,
borderRadius: 5,
backgroundColor: Colors.white
}}
>
<Animated.View style={{
position: 'absolute',
transform: [{ translateX: moveX }, { translateY: moveY }]
}}
>
<Animated.Text style={{
fontSize,
color
}}
>
{label}
</Animated.Text>
</Animated.View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<TextInput
ref={ref}
autoCapitalize="none"
autoCompleteType="off"
autoCorrect={false}
secureTextEntry={secureTextEntry}
onChangeText={(v) => setValue(v)}
style={{
margin: 0,
padding: 12.5,
flex: 1,
fontSize: 15
}}
hitSlop={{
top: 10,
right: 10,
bottom: 10,
left: 10
}}
onFocus={() => setTrans(1)}
onBlur={() => {
if (value.length === 0) {
setTrans(0);
}
}}
returnKeyType={returnKeyType}
/>
<View style={{ padding: 12.5 }}>
{icon}
</View>
</View>
</View>
);
});
export default AuthenticationInput;

Related

Changed from react native class to function and it doesnt work 100%

I am trying to change a class into a function. This is mainly because if I can get it working, I want to use it for learning different animations, I have had some success but not 100%. Originally it displayed an icon that when clicked it spun it one way and then when clicked again it spun the other way. What I have tried to do it get rid of the icon and replace it with an image. It works when clicked once but then does nothing.
I am struggling with toggled aspect of it and setting the state I think because I cant seem to set it up properly in a function.
I have tried several things but this is the best I can get. If I show the original code and then what I have managed to change, maybe someone can point me in the right direction as to what I am doing wrong.
All I want is the image to display and then when clicked spins right and then if clicked again it spins left.
I am doing this so I can mess around with the settings and hopefully learn animation a bit better.
Any help would be greatly appreciated.
The original code :
import React from 'react';
import { View, StyleSheet, Animated, Image, TouchableOpacity } from 'react-native';
import { Ionicons } from '#expo/vector-icons';
const TabIcon = ({
onPress,
menuToggled
}) => {
const logoStyles = [styles.logoStyle];
if (menuToggled !== null) {
const animation = new Animated.Value(menuToggled ? 0 : 1);
Animated.timing(animation, {
toValue: menuToggled ? 1 : 0,
duration: 500,
useNativeDriver: true
}).start();
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
}
return (
<TouchableOpacity
style={styles.tabStyle}
onPress={onPress}
>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/></Animated.View>
</TouchableOpacity>
);
};
export default class App extends React.Component {
state = {
menuToggled: null
}
toggleMenu = () => {
this.setState(prevState => {
return { menuToggled: !prevState.menuToggled };
});
}
render () {
return (
<View style={styles.container}>
<TabIcon
onPress={this.toggleMenu}
menuToggled={this.state.menuToggled}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
and what I have changed so far :
import React, { useRef, useState } from "react";
import { View, StyleSheet, Animated, Image, TouchableOpacity, Easing } from 'react-native';
import Constants from 'expo-constants';
const App = () => {
const spinValue = useRef(new Animated.Value(0)).current;
const [menuToggled, setMenuToggled] = useState([null]);
toggleMenu = () => {
setMenuToggled(menuToggled === "null" ? "menuToggled" : "null");
}
const Spinner = ({
onPress,
menuToggled
}) => {
const logoStyles = [styles.logoStyle];
const animation = new Animated.Value(0);
const go = () => {
Animated.timing(animation, {
toValue: 1,
duration: 1500,
easing: Easing.elastic(1),
useNativeDriver: true
}).start();
}
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
return (
<TouchableOpacity
onPress={go}
>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/></Animated.View>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<Spinner
onPress={toggleMenu}
menuToggled={menuToggled}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
export default App;
There are a few issues. You first had menuToggled initialized to [null] when it should have been null. You also had forgotten to use onPress in TabIcon. The most noteworthy thing was wrapping TabIcon in a useCallback to prevent it from being recreated all the time. Expo snack:
import React, { useRef, useState, useCallback } from 'react';
import {
View,
StyleSheet,
Animated,
Image,
TouchableOpacity,
} from 'react-native';
import Constants from 'expo-constants';
const App = () => {
const spinValue = useRef(new Animated.Value(0)).current;
const [menuToggled, setMenuToggled] = useState(null);
const TabIcon = useCallback(({ onPress, menuToggled }) => {
const logoStyles = [styles.logoStyle];
// initialized base on menuToggled
// if not done then it will take an additional button press to trigger
// the animation
const animation = useRef(new Animated.Value(menuToggled ? 0 : 1)).current;
const startAnimation = () => {
Animated.timing(animation, {
toValue: menuToggled ? 1 :0,
duration: 500,
useNativeDriver: true,
}).start();
};
const rotateInterpolate = animation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
const animatedStyles = { transform: [{ rotate: rotateInterpolate }] };
logoStyles.push(animatedStyles);
return (
<TouchableOpacity
onPress={() => {
startAnimation();
onPress?.();
}}>
<Animated.View style={logoStyles}>
<Animated.Image
style={styles.tinyLogo}
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
}}
/>
</Animated.View>
</TouchableOpacity>
);
},[]);
return (
<View style={styles.container}>
<TabIcon
onPress={() => setMenuToggled((prev) => !prev)}
menuToggled={menuToggled}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
tinyLogo: {
width: 150,
height: 150,
borderRadius: 100,
margin: 8,
},
});
export default App;

Error while animating rotateX in react native

I am trying to rotate an icon on press of a button in my react native application.
But I am getting this error:
Error while updating property 'transform' of a view managed by:
RCTView
This is the Icon itself:
<TouchableOpacity
style={[
styles.expandButton,
{transform: [{rotateX: toString(expandIconAngle) + 'deg'}]},
]}
onPress={() => {
rotateIcon();
}}>
<Icon name="expand-less" color="#ffffff" size={28} />
</TouchableOpacity>
This is the function which is responsible for rotating the icon:
const expandIconAngle = useRef(new Animated.Value(0)).current;
function rotateIcon() {
Animated.timing(expandIconAngle, {
toValue: 180,
duration: 300,
easing: Easing.linear,
}).start();
}
Where am I going wrong?
use interpolate and Animated.Image :
import React, { useRef } from "react";
import { Animated, Text, View, StyleSheet, Button, SafeAreaView,Easing,TouchableOpacity } from "react-native";
const App = () => {
// fadeAnim will be used as the value for opacity. Initial Value: 0
const angle = useRef(new Animated.Value(0)).current;
const fadeOut = () => {
// Will change fadeAnim value to 0 in 3 seconds
Animated.timing(
angle,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
};
const spin =angle.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<SafeAreaView style={styles.container}>
<Animated.Image
style={{transform: [{rotateX: spin}] }}
source={require('#expo/snack-static/react-native-logo.png')} />
<TouchableOpacity onPress={fadeOut} style={styles.buttonRow}>
<Text>Button</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center"
},
buttonRow: {
flexBasis: 100,
justifyContent: "space-evenly",
marginVertical: 16
}
});
export default App;
LIVE example - https://snack.expo.dev/TP-fQExXT

React Native Redash, TypeError: this.props.onScroll is not a function

I got errors with useValue and interpolateColor and onScrollEvent!!!
I checked the docs in github and it seams that since version 15.0.0 we need to import the above from v1 like:
import { useValue } from 'react-native-redash/src/v1/Hooks'
import { interpolateColor } from 'react-native-redash/src/v1/Colors'
import { onScrollEvent } from 'react-native-redash/src/v1/Gesture'
Now I got no errors at the beginning, but I get a blank screen and if I try to scroll, I get the following error:
TypeError: this.props.onScroll is not a function. (In 'this.props.onScroll(e)', 'this.props.onScroll' is an instance of AnimatedEvent)
Is there something wrong with the imports?
The rest of the code is:
const { width } = Dimensions.get('window')
export interface OnboardingProps {
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white'
},
slider: {
height: SLIDE_HEIGHT,
borderBottomRightRadius: 75
},
footer: {
flex: 1
}
})
const Onboarding: React.SFC<OnboardingProps> = () => {
const x = useValue(0)
// TODO: useScrollEvent
const onScroll = onScrollEvent({ x });
const backgroundColor = interpolateColor(x, {
inputRange: [0, width, width * 2, width * 3],
outputRange: ["#2ECC71", "#5DADE2", "#82E0AA", "#5499C7"]
})
return (
<View style={styles.container}>
<Animated.View style={[styles.slider, { backgroundColor }]}>
<Animated.ScrollView horizontal snapToInterval={width}
decelerationRate='fast' showsHorizontalScrollIndicator={false}
bounces={false}
{...{ onScroll }}
>
<Slide label='Relaxed' />
<Slide label='Playful' right />
<Slide label='Exentric' />
<Slide label='Funky' right />
</Animated.ScrollView>
</Animated.View>
<View style={styles.footer} >
<Animated.View style={{ ...StyleSheet.absoluteFillObject, backgroundColor }} >
<View style={{ flex: 1, backgroundColor: 'white', borderTopLeftRadius: 75 }} ></View>
</Animated.View>
</View>
</View>
);
}
The instructor (William Candillon) has a TODO: useScrollEvent note. Probably wants to try later to use that in place of onScrollEvent. Where is the useScrollEvent to be found?
I also get an error with the style that uses the backgroundColor:
...
const backgroundColor = interpolateColor(x, {
inputRange: [0, width, width * 2, width * 3],
outputRange: ["#2ECC71", "#5DADE2", "#82E0AA", "#5499C7"]
})
...
<Animated.View style={[styles.slider, { backgroundColor}]}>
Namely the word style is underlined with red.
I use a normal color the error goes away:
<Animated.View style={[styles.slider, { backgroundColor: 'cyan'}]}>
The code is from YouTube
Any suggestions ?
Thanks in advance!

React Native - Touchable Opacity inside Animated.View is firing event of background list view

I Have a list with scroll view, I am trying to add filter options for it. When click on a filter icon, an overlay with position:absolute will be displayed inside a Animated.View. I have Buttons inside overlay View with TouchableOpacity
Filter.js
export default class FilterFade extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible,
};
};
componentWillMount() {
this._visibility = new Animated.Value(this.props.visible ? 1 : 0);
}
componentWillReceiveProps(nextProps) {
if (nextProps.visible) {
this.setState({ visible: true });
}
Animated.timing(this._visibility, {
toValue: nextProps.visible ? 1 : 0,
duration: 300,
}).start(() => {
this.setState({ visible: nextProps.visible });
});
}
render() {
const { visible, style, children, ...rest } = this.props;
const containerStyle = {
opacity: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
transform: [
{
scale: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [1.1, 1],
}),
},
],
};
const combinedStyle = [containerStyle, style];
return (
<Animated.View style={combinedStyle} {...rest}>
{children}
</Animated.View>
);
}
}
View.js
<FilterFade visible={this.state.isFilterVisible}>
<View style={styles.filterView}>
<TouchableOpacity onPress={() => this.getFilteedStories}>
<Text style={styles.filterOption}> My Stories </Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.getFilteedStories}>
<Text style={styles.filterOption}> All Stories </Text>
</TouchableOpacity>
</View>
</FilterFade>
Styles
filterView :{
position: 'absolute',
top: 0,
right: 5,
backgroundColor: #CCC,
width: 150,
paddingTop: 15,
paddingBottom: 15,
zIndex: 999,
},
filterOption: {
color: "#FFF",
fontSize: 15
}
Now, When I click on TouchableOpacity Text in Filter, the click event is triggered in Listview which is behind the FadeView.
Can Some one please let me know on how to add press event inside a Animated absolute view.
Thanks in Advance.
Use TouchableOpacity from 'react-native-gesture-handler' instead of from 'react-native'.
import { TouchableOpacity } from 'react-native-gesture-handler';
Follow this post Cannot click TouchableOpacity in animated.view using React native

Parallax/ animated Header on react native with scrollView onScroll

With this reference
Problems with parallax header in react native
The only solution found is just an hack that hide you refreshcomponent because contentContainerStyle don't interact with the refreshcomponent.
So, the only solution is to move the scrollview component, but moving it while you are scrolling is pretty laggy and staggering.
Any solution?
This is pretty common case, i mean..Facebook app and Twitter app have both this this type of home screen!
and example animation is:
animated header from play store app home
added snack:
snack esample of header animation
as you see, on Android, scrolling up and down start to stagger because the 2 animation (container and scroll) are concurrent: they don't mix, each one try to animate ..going mad.
UPDATE 3: snack solution good for android and ios
update: complete snack with gif like animation
I found a workaround for the first partial solution (absolute header with transform translate and contentContainerStyle with paddingTop)
The problem is only on the refresh component, so what to do?
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
<AnimatedFlatList
data={data}
renderItem={item => this._renderRow(item.item, item.index)}
scrollEventThrottle={16}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.state.scrollAnim } }, },
], { useNativeDriver: true })}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={() => {
this.setState({ refreshing: true });
setTimeout(() => this.setState({ refreshing: false }), 1000);
}}
// Android offset for RefreshControl
progressViewOffset={NAVBAR_HEIGHT}
/>
}
// iOS offset for RefreshControl
contentInset={{
top: NAVBAR_HEIGHT,
}}
contentOffset={{
y: -NAVBAR_HEIGHT,
}}
/>
This apply offset style on the refreshController, aligning it with the content.
UPDATE2:
there are some problems on ios.
UPDATE3:
fixed on ios too.
snack working both ios and android
You can try the react-spring library as it supports Parallax effects for react native.
Update: A working solution from your example
import React, { Component } from 'react';
import { Animated, Image, Platform, StyleSheet, View, Text, FlatList } from 'react-native';
const data = [
{
key: 'key',
name: 'name',
image: 'imageUrl',
},
];
const NAVBAR_HEIGHT = 90;
const STATUS_BAR_HEIGHT = Platform.select({ ios: 20, android: 24 });
const styles = StyleSheet.create({
fill: {
flex: 1,
},
navbar: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
alignItems: 'center',
backgroundColor: 'white',
borderBottomColor: '#dedede',
borderBottomWidth: 1,
height: NAVBAR_HEIGHT,
justifyContent: 'center',
paddingTop: STATUS_BAR_HEIGHT,
},
contentContainer: {
flex: 1,
},
title: {
color: '#333333',
},
row: {
height: 300,
width: null,
marginBottom: 1,
padding: 16,
backgroundColor: 'transparent',
},
rowText: {
color: 'white',
fontSize: 18,
},
});
export default class App extends Component {
constructor(props) {
super(props);
const scrollAnim = new Animated.Value(0);
this._clampedScrollValue = 0;
this._offsetValue = 0;
this._scrollValue = 0;
this.state = {
scrollAnim,
};
}
_renderRow(rowData, rowId) {
return (
<View style={{ flex: 1 }}>
<Image key={rowId} style={styles.row} source={{ uri: rowData.image }} resizeMode="cover" />
<Text style={styles.rowText}>{rowData.title}</Text>
</View>
);
}
render() {
const { scrollAnim } = this.state;
const navbarTranslate = scrollAnim.interpolate({
inputRange: [0, NAVBAR_HEIGHT - STATUS_BAR_HEIGHT],
outputRange: [0, -(NAVBAR_HEIGHT - STATUS_BAR_HEIGHT)],
extrapolate: 'clamp',
});
const navbarOpacity = scrollAnim.interpolate({
inputRange: [0, NAVBAR_HEIGHT - STATUS_BAR_HEIGHT],
outputRange: [1, 0],
extrapolate: 'clamp',
});
return (
<View style={styles.fill}>
<View style={styles.contentContainer}>
<FlatList
data={data}
renderItem={item => this._renderRow(item.item, item.index)}
scrollEventThrottle={16}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.state.scrollAnim } } },
])}
/>
</View>
<Animated.View style={[styles.navbar, { transform: [{ translateY: navbarTranslate }] }]}>
<Animated.Text style={[styles.title, { opacity: navbarOpacity }]}>PLACES</Animated.Text>
</Animated.View>
</View>
);
}
}
In case anybody else falls on this thread, I developed a new package, react-native-animated-screen, that does exactly what you need
Check it out
https://www.npmjs.com/package/react-native-animated-screen