I am trying to implement an animation that is supposed to be run on iOS and Android devices.
In iOS the performance seems satisfactory (tested with iPhone 6 Plus and up).
On the other hand for some Android devices the animations lag behind.
The question is, what kind of actions can be taken to avoid the performance problem (apart from the usage of useNativeDriver={true}
directive, which is already in the code)?
The code is like that:
import * as React from 'react';
import {
Animated,
ImageBackground,
StyleSheet,
Image,
Easing,
TextInput,
View,
Text,
} from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import backImg from './background.png';
const c_initial_coordinate_left = 100;
const c_initial_coordinate_top = 100;
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.spaceAnimatedTranslations = new Animated.ValueXY();
this.spaceAnimatedTranslations2 = new Animated.ValueXY();
this.spaceAnimatedTranslations3 = new Animated.ValueXY();
this.spaceAnimatedTranslations4 = new Animated.ValueXY();
this.spaceAnimatedTranslations5 = new Animated.ValueXY();
this.spaceAnimatedTranslations.addListener(value => (this.spaceAnimatedTranslations_value = value));
this.spaceAnimatedTranslations2.addListener(value => (this.spaceAnimatedTranslations_value2 = value));
this.spaceAnimatedTranslations3.addListener(value => (this.spaceAnimatedTranslations_value3 = value));
this.spaceAnimatedTranslations4.addListener(value => (this.spaceAnimatedTranslations_value4 = value));
this.spaceAnimatedTranslations5.addListener(value => (this.spaceAnimatedTranslations_value5 = value));
this._animatedStyle = {transform: [{ translateX: this.spaceAnimatedTranslations.x }, { translateY: this.spaceAnimatedTranslations.y },],};
this._animatedStyle2 = {transform: [{ translateX: this.spaceAnimatedTranslations2.x }, { translateY: this.spaceAnimatedTranslations2.y },],};
this._animatedStyle3 = {transform: [{ translateX: this.spaceAnimatedTranslations3.x }, { translateY: this.spaceAnimatedTranslations3.y },],};
this._animatedStyle4 = {transform: [{ translateX: this.spaceAnimatedTranslations4.x }, { translateY: this.spaceAnimatedTranslations4.y },],};
this._animatedStyle5 = {transform: [{ translateX: this.spaceAnimatedTranslations5.x }, { translateY: this.spaceAnimatedTranslations5.y },],};
}
onSpaceMove(event) {
let l_panTranslateX = event.nativeEvent.translationX;
let l_panTranslateY = event.nativeEvent.translationY;
let l_panStartX = event.nativeEvent.x - event.nativeEvent.translationX;
let l_panStartY = event.nativeEvent.y - event.nativeEvent.translationY;
let l_animationsArray = new Array();
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations2.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations3.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations4.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations5.y, {toValue: event.nativeEvent.translationY,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations2.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations3.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations4.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
l_animationsArray.push(Animated.timing(this.spaceAnimatedTranslations5.x, {toValue: event.nativeEvent.translationX,duration: 0,easing: Easing.linear,}));
Animated.parallel(l_animationsArray).start();
this.debug_message = `\n
event.nativeEvent.translationX: ${Math.floor(
event.nativeEvent.translationX
)}
event.nativeEvent.translationY: ${Math.floor(
event.nativeEvent.translationY
)}
event.nativeEvent.absoluteX: ${Math.floor(event.nativeEvent.absoluteX)}
event.nativeEvent.absoluteY: ${Math.floor(event.nativeEvent.absoluteY)}
event.nativeEvent.x: ${Math.floor(event.nativeEvent.x)}
event.nativeEvent.y: ${Math.floor(event.nativeEvent.y)}
this.spaceAnimatedTranslations_value.x: ${Math.floor(
this.spaceAnimatedTranslations_value.x
)}
this.spaceAnimatedTranslations_value.y: ${Math.floor(
this.spaceAnimatedTranslations_value.y
)}
this.gestureStartedX: ${Math.floor(this.gestureStartedX)}
this.gestureStartedY: ${Math.floor(this.gestureStartedY)}
`;
this.forceUpdate();
}
onSpaceMoveCompleted(event) {
if (event.nativeEvent.state === State.BEGAN) {
this.spaceAnimatedTranslations.flattenOffset();
this.spaceAnimatedTranslations2.flattenOffset();
this.spaceAnimatedTranslations3.flattenOffset();
this.spaceAnimatedTranslations4.flattenOffset();
this.spaceAnimatedTranslations5.flattenOffset();
this.spaceAnimatedTranslations.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
this.spaceAnimatedTranslations2.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
this.spaceAnimatedTranslations3.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
this.spaceAnimatedTranslations4.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top),});
this.spaceAnimatedTranslations5.setOffset({x: Math.floor(event.nativeEvent.absoluteX - c_initial_coordinate_left),y: Math.floor(event.nativeEvent.absoluteY - c_initial_coordinate_top ),});
this.gestureStartedX = event.nativeEvent.absoluteX;
this.gestureStartedY = event.nativeEvent.absoluteY;
}
if (event.nativeEvent.state === State.END) {
this.onSpaceMove(event);
}
}
render() {
return (
<ImageBackground source={backImg} style={{ flex: 1 }}>
{this.debug_message ? (
<Text style={{ color: 'white' }}>{this.debug_message}</Text>
) : (
undefined
)}
<PanGestureHandler
key={`test`}
onGestureEvent={e => this.onSpaceMove(e)}
onHandlerStateChange={e => this.onSpaceMoveCompleted(e)}>
<Animated.View
ref={ref => {
this.testAnimatedView = ref;
}}
style={[styles._animatable_view, this._animatedStyle]}
useNativeDriver={true}>
<View style={styles._box_content}>
<Text
style={{
width: '100%',
height: '100%',
fontSize: 15,
textAlign: 'center',
textAlignVertical: 'center',
borderRadius: 20,
}}
editable={false}
ref={ref => {
this.textInputRef = ref;
}}
>
{'Master (1) (DRAG THIS ONE)'}
</Text>
</View>
</Animated.View>
</PanGestureHandler>
<Animated.View
ref={ref => {
this.testAnimatedView2 = ref;
}}
style={[styles._animatable_view2, this._animatedStyle2]}
useNativeDriver={true}>
<View style={styles._box_content}>
<Text
style={{
width: '100%',
height: '100%',
fontSize: 15,
textAlign: 'center',
textAlignVertical: 'center',
borderRadius: 20,
}}
editable={false}
ref={ref => {
this.textInputRef = ref;
}}
>
{'Slave (2) '}
</Text>
</View>
</Animated.View>
<Animated.View
ref={ref => {
this.testAnimatedView3 = ref;
}}
style={[styles._animatable_view3, this._animatedStyle3]}
useNativeDriver={true}>
<View style={styles._box_content}>
<Text
style={{
width: '100%',
height: '100%',
fontSize: 15,
textAlign: 'center',
textAlignVertical: 'center',
borderRadius: 20,
}}
editable={false}
ref={ref => {
this.textInputRef = ref;
}}
>
{'Slave (3) '}
</Text>
</View>
</Animated.View>
<Animated.View
ref={ref => {
this.testAnimatedView4 = ref;
}}
style={[styles._animatable_view4, this._animatedStyle4]}
useNativeDriver={true}>
<View style={styles._box_content}>
<Text
style={{
width: '100%',
height: '100%',
fontSize: 15,
textAlign: 'center',
textAlignVertical: 'center',
borderRadius: 20,
}}
editable={false}
ref={ref => {
this.textInputRef = ref;
}}
>
{'Slave (4) '}
</Text>
</View>
</Animated.View>
<Animated.View
ref={ref => {
this.testAnimatedView5 = ref;
}}
style={[styles._animatable_view5, this._animatedStyle5]}
useNativeDriver={true}>
<View style={styles._box_content}>
<Text
style={{
width: '100%',
height: '100%',
fontSize: 15,
textAlign: 'center',
textAlignVertical: 'center',
borderRadius: 20,
}}
editable={false}
ref={ref => {
this.textInputRef = ref;
}}
>
{'Slave (5) '}
</Text>
</View>
</Animated.View>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
_animatable_view: {
flex: 1,
width: 250,
height: 50,
top: c_initial_coordinate_top,
left: c_initial_coordinate_left,
position: 'absolute',
backgroundColor: '#ABC',
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view2: {
flex: 1,
width: 250,
height: 50,
top: c_initial_coordinate_top + 100,
left: c_initial_coordinate_left,
position: 'absolute',
backgroundColor: '#ABC',
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view3: {
flex: 1,
width: 250,
height: 50,
top: c_initial_coordinate_top + 200,
left: c_initial_coordinate_left,
position: 'absolute',
backgroundColor: '#ABC',
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view4: {
flex: 1,
width: 250,
height: 50,
top: c_initial_coordinate_top + 300,
left: c_initial_coordinate_left,
position: 'absolute',
backgroundColor: '#ABC',
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view5: {
flex: 1,
width: 250,
height: 50,
top: c_initial_coordinate_top + 400,
left: c_initial_coordinate_left,
position: 'absolute',
backgroundColor: '#ABC',
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
},
_box_content: {
flex: 1,
height: '100%',
width: '100%',
borderRadius: Math.min(this.rectangleHeight, this.rectangleWidth) / 2,
alignItems: 'center',
justifyContent: 'center',
borderColor: 'gainsboro',
borderWidth: 2,
opacity: 0.9,
},
});
This can be seen in action in this snack: https://snack.expo.io/@mehmetkaplan/movetextwithgesturesingle
Additionally, in order to feel the performance problem I generated another snack, which simply animates 5 objects simultaneously. If you run this through low end Android devices, you can feel the performance problem: https://snack.expo.io/@mehmetkaplan/movetextwithgesturemulti