React Native Animated - Card Won't flip back - javascript

I've been messing around with React Native and I've tried to create a simple card that flips around on press (back and forth):
I had it working as a class component but now I've tried to refactor to a functional component and its only flipping one way but not back on second press.
Can someone advise me what I've missed? :/
I tried using useState and useEffect for the value and animated value etc, but no dice...
import React from 'react';
import { View, StyleSheet, Animated, TouchableWithoutFeedback, Text } from 'react-native';
import TabBarIcon from '../components/TabBarIcon';
const App = () => {
let animatedValue = new Animated.Value(0);
let value = 0;
animatedValue.addListener(({ value }) => {
this.value = value;
})
let frontInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
})
let backInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
let frontOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0]
});
let backOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1]
});
let elevationFront = animatedValue.interpolate({
inputRange: [0, 25],
outputRange: [10, 0]
})
let elevationBack = animatedValue.interpolate({
inputRange: [155, 180],
outputRange: [0, 10]
})
const flipCard = () => {
if (value >= 90) {
Animated.spring(animatedValue,{
toValue: 0,
friction: 8,
tension: 10
}).start();
} else {
Animated.spring(animatedValue,{
toValue: 180,
friction: 8,
tension: 10
}).start();
}
}
const frontAnimatedStyle = {
transform: [{ rotateY: frontInterpolate}]
}
const backAnimatedStyle = {
transform: [{ rotateY: backInterpolate }]
}
return (
<TouchableWithoutFeedback onPress={() => flipCard()} >
<View>
<Animated.View style={[frontAnimatedStyle, styles.paperFront,{elevation: elevationFront}, {opacity: frontOpacity}]}>
<Text style={{fontSize: 20,paddingTop: 8, paddingLeft: 8, color: 'black',lineHeight: 20}}>
Title Front {value} - <Text style={{fontSize: 8}}>KPI</Text>
</Text>
<View style={{position: "absolute", paddingTop: 3, right: 8}}>
<TabBarIcon style={{color: "black"}} name={"md-more"} />
</View>
</Animated.View>
<Animated.View style={[backAnimatedStyle, styles.paperBack, {elevation: elevationBack}, {opacity: backOpacity}]}>
<Text>Back title {value}</Text>
</Animated.View>
</View>
</TouchableWithoutFeedback>
);
}
const styles = StyleSheet.create({
paperFront : {
marginHorizontal: 15,
backgroundColor: "white",
minHeight: 200,
borderRadius: 5,
marginBottom: 15,
},
paperBack : {
top: -215,
marginHorizontal: 15,
backgroundColor: "white",
minHeight: 200,
borderRadius: 5,
marginBottom: 15,
}
});
export default App

You need to call your "value" not "this.value", but since you already got "value" parameter I suggest you change the variable name:
let val = 0;
animatedValue.addListener(({ value}) => {
val = value;
});
P.S: don't forget to change your calls to "value" variable into "val"

fix it
if (this.value >= 90) {
in expo

Related

How do I create a flippable card in react-native?

Specifically, how do I make one side of the card disappear once flipped. I am using Android, so backfaceVisibility did not help solve my problem. I used animate to remove the object via opacity, but the problem is that once I removed one side of the card, the 'invisible' buttons still worked, and the buttons on the current side of the card did not. I tried playing around with zIndex and 'disabled' in Pressable, and I am unsure how to fix this. I have attached source code below:
let animatedValue = new Animated.Value(0)
let val = 0;
animatedValue.addListener(({ value }) => {
val = value;
})
let frontOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0]
})
let backOpacity = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1]
})
let frontInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
})
let backInterpolate = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
const frontAnimatedStyle = {
transform: [
{ rotateY: frontInterpolate }
],
opacity: frontOpacity,
}
const backAnimatedStyle = {
transform: [
{ rotateY: backInterpolate }
],
opacity: backOpacity,
}
let isFront = true;
const flipCard = () => {
isFront = !isFront;
if (val >= 90) {
Animated.spring(animatedValue, {
toValue:0,
friction: 8,
tension: 10,
useNativeDriver: true,
}).start();
} else {
Animated.spring(animatedValue, {
toValue:180,
friction: 8,
tension: 10,
useNativeDriver: true,
}).start();
}
}
const getFrontZ = () => {
return isFront ? 1 : 0;
}
return(
<View style={styles.container}>
<Animated.View
style={[styles.cardStyle,
styles.frontCardStyle,
frontAnimatedStyle,
{height: cardHeight, zIndex: -1,}]}>
<Pressable
onPress={() => console.log('Open Stack')}
style={{height: '100%', width: '100%'}}>
<Pressable
onPress={() => flipCard()}
style={{backgroundColor: 'blue', height: 50, width: 50,}}
>
</Pressable>
</Pressable>
</Animated.View>
<Animated.View
style={[styles.cardStyle,
styles.backCardStyle,
backAnimatedStyle,
{height: cardHeight, zIndex: 0}]}>
<Pressable
onPress={() => flipCard()}
style={{height: '100%', width: '100%'}}
>
</Pressable>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
cardStyle: {
height: '100%',
width: '100%',
backfaceVisibility: 'hidden',
},
frontCardStyle: {
backgroundColor: 'red',
},
backCardStyle: {
backgroundColor: 'purple',
position: 'absolute',
top: 0,
}
})
try this library react-native-card-flip
and import this in your file and use like this
import CardFlip from 'react-native-card-flip';
<CardFlip style={styles.cardContainer} ref={(card) => this.card = card} >
<TouchableOpacity style={styles.card} onPress={() => this.card.flip()} >
<Text>AB</Text></TouchableOpacity>
<TouchableOpacity style={styles.card} onPress={() => this.card.flip()} >
<Text>CD</Text></TouchableOpacity>
</CardFlip>

Custom Textinput shows border around itself in IOS - React Native

I've created a custom text input component for my react native app. It has no issues in the android build. But In IOS, the custom component draws a border around its view.
This is how it looks in ios. You can see a border around it.
I want it took to look like this in IOS without a square border
This is my custom component (MyInput.js)
import React, {useState, useRef, useEffect} from 'react';
import {
Animated,
Text,
ActivityIndicator,
TouchableOpacity,
} from 'react-native';
const {View, TextInput, StyleSheet} = require('react-native');
const MyInput = (props) => {
const [hide, makeHide] = useState(false);
const [phAanimatedValue] = useState(new Animated.Value(0));
const [labelAanimatedValue] = useState(new Animated.Value(0));
const [overflow, setOverflow] = useState('hidden');
const movePlaceHolderLeft = phAanimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -10],
});
const moveLabelRight = labelAanimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-10, 0],
});
const animatePlaceHolder = (v, hide) => {
Animated.timing(phAanimatedValue, {
toValue: v,
duration: 50,
useNativeDriver: true,
}).start(() => {
makeHide(hide);
animateLabel(v);
});
};
const animateLabel = (v) => {
setOverflow('visible');
Animated.timing(labelAanimatedValue, {
toValue: v,
duration: 50,
useNativeDriver: true,
}).start();
};
return (
<View
style={[
styles.input,
{
overflow: overflow,
borderColor:props.error?"#ff0033":"#e5deda",
},
]}>
<TextInput
onFocus={() => {
animatePlaceHolder(1, true);
}}
ref={props.cref}
value={props.value}
onChangeText={(v) => props.onChangeText(v)}
style={{paddingStart: 25}}
onBlur={() => {
if (props.value.length > 0) {
makeHide(true);
} else {
animatePlaceHolder(0, false);
}
}}
/>
{!hide ? (
<Animated.Text
onPress={() => props.cref.current.focus()}
style={{
position: 'absolute',
transform: [{translateX: movePlaceHolderLeft}],
height: '100%',
fontFamily: 'Muli-Regular',
width: '100%',
marginStart: 20,
paddingVertical:15,
color: props.error ? '#ff0033' : '#a6a6a6',
textAlignVertical: 'center',
}}>
{props.label}
</Animated.Text>
) : (
<Animated.Text
onPress={() => props.cref.current.focus()}
style={{
position: 'absolute',
transform: [{translateX: moveLabelRight}],
marginTop: -10,
zIndex:1000,
backgroundColor: 'white',
color:props.error?'red':'#e5deda',
marginStart: 20,
fontFamily: 'Muli-Regular',
paddingHorizontal: 5,
textAlignVertical: 'top',
}}>
{props.label}
</Animated.Text>
)}
</View>
);
};
const styles = StyleSheet.create({
input: {
borderWidth: 2,
borderColor: '#e5deda',
backgroundColor: 'white',
fontSize: 14,
color: '#353839',
paddingVertical:15,
marginHorizontal: 20,
lineHeight: 18,
fontFamily: 'Muli-Regular',
fontWeight: '600',
borderRadius: 100,
},
});
export default MyInput;
This is How I Render:
<MyInput
value={value}
onChangeText={(v)=>
{
setValue(v)
setError(true)
}}
cref={postalRef}
error={error}
label="Postal Code"
/>
Is there any solution for this or is this a bug?

How to animate a View sticky at a certain position on scrolling in React Native?

I'm trying to animate a View to be sticky to a certain position when scrolling. The scrolling component I use is FlatList.
Here's what I've come so far:
ProfileScreen
Basically, this is the structure of my ProfileScreen.js:
<View>
<Animated.View /> --> The animated cover photo (includes the animated avatar as well)
<Animated.FlatList /> --> This renders the list of images you see in the video. And a ListHeaderComponent renders the content from the name to the View of follow numbers
</View
Now what I want to achieve is:
when the user scrolls the whole FlatList and it reachs the position where the View containing the follow numbers reaches the bottom border of the header, that follow View will sticks there. (the follow View must scrolls along with th FlatList)
Just like a sticky tab view.
You can imagine my "goal" screen is like the profile screen of Twitter.
Here's the full code:
//...
const ProfileScreen = (props) => {
const [index, setIndex] = useState(0);
const [isFollowed, setIsFollowed] = useState(false);
const [firstFollow, setFirstFollow] = useState(false);
const [enableScrollView, setEnableScrollView] = useState(false);
const trips = useSelector(state => state.trips.availableTrips);
const scrollY = useRef(new Animated.Value(0)).current;
const headerHeight = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT], // [0, 50]
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT], // [120,70]
extrapolate: 'clamp'
});
const profileImageHeight = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT-10],
outputRange: [PROFILE_IMAGE_MAX_HEIGHT,PROFILE_IMAGE_MIN_HEIGHT],
extrapolate: 'clamp'
});
const profileImageMarginTop = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
outputRange: [
HEADER_MAX_HEIGHT - PROFILE_IMAGE_MAX_HEIGHT / 2,
HEADER_MIN_HEIGHT /2 - PROFILE_IMAGE_MIN_HEIGHT/2
],
extrapolate: 'clamp'
});
const profileImageMarginLeft = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
outputRange: [
WIDTH/2 - PROFILE_IMAGE_MAX_HEIGHT/2,
10
],
extrapolate: 'clamp'
})
const headerZindex = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT, 120],
outputRange: [0, 0, 1000],
extrapolate: 'clamp'
});
const headerTitleColor = scrollY.interpolate({
inputRange: [0, 50, 70, 80, 90, 100],
outputRange: ['transparent', 'transparent', 'transparent', 'transparent', 'transparent', 'white'],
extrapolate: 'extend'
});
const tabBarPosition = scrollY.interpolate({
inputRange: [0,200],
outputRange: [(HEADER_MAX_HEIGHT + PROFILE_IMAGE_MIN_HEIGHT)*2, HEADER_MIN_HEIGHT],
extrapolate: 'clamp'
})
const _render_Sitcky_Info_View = () => {
return(
<View style={{marginTop: HEADER_MAX_HEIGHT+ PROFILE_IMAGE_MAX_HEIGHT/2}}>
<InfoDisplay
isFollowed={isFollowed}
onFollow={onFollow}
userName={profile.userName}
tripsNumber={trips.length}
navigateToTripsListScreen={() => props.navigation.navigate('TripsListScreen')}
/>
</View>
)
}
return(
<SafeAreaView style={styles.container}>
<Animated.View style={[styles.backgroundImage, {
height: headerHeight,
zIndex: headerZindex,
elevation: headerZindex,
justifyContent: 'center'
}]}>
<Animated.Image
source={require('../../../assets/images/beach.jpg')}
style={{
flex: 1,
}}
/>
<View style={{
position: 'absolute',
left: 60,
}}>
<Animated.Text style={{color: headerTitleColor, fontSize: 20, fontWeight: 'bold'}}>{profile.userName}</Animated.Text>
</View>
<Animated.View style={[styles.profileImgContainer,{
height: profileImageHeight,
width: profileImageHeight,
borderRadius: PROFILE_IMAGE_MAX_HEIGHT/2,
position: 'absolute',
top: profileImageMarginTop,
left: profileImageMarginLeft,
alignSelf: 'center'
}]}>
<Image source={{uri: profile.userAvatar}} style={styles.profileImg} />
</Animated.View>
</Animated.View>
<Animated.FlatList
style={{flex: 1, backgroundColor: 'transparent'}}
contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}
scrollEventThrottle={16}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: scrollY}}}],
)}
showsVerticalScrollIndicator={false}
bounces={true}
data={imgData}
numColumns={NUM_COLUMNS}
keyExtractor={item => item.id}
renderItem={(itemData) => {
return(
<TouchableOpacity style={{
marginHorizontal: findMidOfThree(imgData, itemData.index) ? 0 : 5 ,
marginVertical: 3,
shadowColor: 'black',
shadowOpacity: 0.26,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 7,
}}>
<Image source={itemData.item.source} style={{
height: WIDTH/3.5,
width: WIDTH/3.5,
borderColor: 'white',
borderWidth: 2,
borderRadius: 10,
}} />
</TouchableOpacity>
)
}}
ListHeaderComponent={_render_Sitcky_Info_View}
{...props}
>
</Animated.FlatList>
</SafeAreaView>
);
};
export default ProfileScreen;
const styles = StyleSheet.create({
//...
})
The InfoDisplay contains the follow numbers View. I know I will have to separate it and bring it to the ProfileScreen. I also tried to animate it with the tabBarPosition, but it didn't work like I thought it'd
You can imagine my "goal" screen is like Twitter profile screen, where my follow numbers View acts like the Tab View in Twitter
https://drive.google.com/file/d/1ERt_7gnPgiwXPg-WODXSnrOZ10kQgPQl/view?usp=drivesdk
Please HELP me. I'd be very grateful!
Calculate the distance need to move by the follow View as shown in the image (let it be x).
Here I am taking 500 as an offset. you can choose any number greater than x. (or use screen height)
const range = [x, x+500];
const translateY = scrollY.interpolate({inputRange:range, outputRange:range, extrapolateLeft: 'clamp'});
ie, if x = 200;
const translateY = scrollY.interpolate({inputRange:[200, 700], outputRange:[200, 700], extrapolateLeft: 'clamp'});
The logic is that, when the follow View reaches top while scrolling (ie, moved distance x). it will start translating down with the same speed of scrolling.
Provide this translateY to the follow View.
<Animated.View style={{...styles, transform: [{translateY}]}} /> // your follow View
I hope this will helps you!

Nested PinchGestureHandler & PanGestureHandler not working in Android

I am trying to build a full screen image view with zoom and pan capabilities in react native. I am using react-native-gesture-handler for handling multi touches. It is working fine on iOS but does nothing on Android.
I am completely confused on why this code would work on iOS but not on Android. I can confirm that my react-native-gesture-handler setup is working because I have another PanGestureHandler working as expected.
/*********************** Imports ***********************/
import React, { useRef, useEffect } from 'react';
import {
View, StyleSheet, Animated,
} from 'react-native';
import ZoomImage from './ZoomableImage';
import { vw, vh, isiPhoneX } from '../Styles/StyleUtils';
/********************* End Imports *********************/
/*********************** ImageView Function ***********************/
const ImageView = ({ dismiss, uri, imageLayout }) => {
const animation = useRef(new Animated.Value(0));
useEffect(() => {
Animated.timing(animation.current, {
toValue: 1,
duration: 250,
}).start();
}, []);
function closeImage() {
Animated.timing(animation.current, {
toValue: 0,
duration: 250,
}).start(() => dismiss());
}
return (
<View style={styles.mainContainer}>
<Animated.View style={[
styles.container,
{
backgroundColor: animation.current.interpolate({
inputRange: [0, 1],
outputRange: ["rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0.5)"],
})
}
]}>
{/* <View style={styles.header}>
<TouchableOpacity style={styles.closeBtn} onPress={closeImage}>
<Icon name="close" color={blackColor} size={30} />
</TouchableOpacity>
</View> */}
<ZoomImage dismiss={closeImage} imageStyle={[
{
left: animation.current.interpolate({
inputRange: [0, 1],
outputRange: [imageLayout.pageX, 0] // 0 : 150, 0.5 : 75, 1 : 0
}),
top: animation.current.interpolate({
inputRange: [0, 1],
outputRange: [imageLayout.pageY, vh(24)] // 0 : 150, 0.5 : 75, 1 : 0
}),
width: animation.current.interpolate({
inputRange: [0, 1],
outputRange: [imageLayout.width, vw(100)],
}),
height: animation.current.interpolate({
inputRange: [0, 1],
outputRange: [imageLayout.height, vw(100)],
})
}
]}
source={{ uri: uri }} />
</Animated.View>
</View>
);
};
/********************** End ImageView Function *******************/
export default ImageView;
const styles = StyleSheet.create({
header: {
marginTop: isiPhoneX() ? 40 : 25,
alignItems: "flex-end"
},
closeBtn: {
paddingHorizontal: 20,
},
mainContainer: {
position: "absolute",
width: vw(100),
height: vh(100)
},
container: {
position: "absolute",
top: 0,
left: 0,
right: 0, bottom: 0,
},
image: {
position: "absolute",
}
});
/*********************** Imports ***********************/
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { Animated } from 'react-native';
import {
State,
PanGestureHandler,
PinchGestureHandler
} from 'react-native-gesture-handler';
import { vw, vh } from '../Styles/StyleUtils';
/********************* End Imports *********************/
/*********************** ZoomableImage Function ***********************/
const ZoomableImage = (props) => {
const panRef = useRef();
const pinchRef = useRef();
const closeAnimation = useRef(new Animated.ValueXY({ x: 0, y: 0 }));
const scaleAnimation = useRef(new Animated.Value(1));
const baseScale = useRef(new Animated.Value(1));
const scale = useRef(Animated.multiply(baseScale.current, scaleAnimation.current));
const [lastScale, setLastScale] = useState(1);
useEffect(() => {
console.log('Refs', panRef);
}, [panRef.current]);
const onPanHandlerGesture = useCallback(({ nativeEvent }) => {
console.log('Native Event', nativeEvent);
closeAnimation.current.setValue({
x: nativeEvent.translationX,
y: nativeEvent.translationY
});
}, []);
const onPanHandlerStateChange = useCallback(({ nativeEvent }) => {
console.log('New Pan Event', nativeEvent);
if (nativeEvent.oldState === State.ACTIVE) {
if (
nativeEvent.translationY > 250
|| nativeEvent.velocityY > 1200
) {
Animated.parallel([
Animated.timing(scaleAnimation.current, {
toValue: 1,
duration: 200
}),
Animated.timing(baseScale.current, {
toValue: 1,
duration: 200
}),
Animated.timing(closeAnimation.current, {
toValue: { x: 0, y: 0 },
duration: 200
})
]).start(() => props.dismiss());
}
else {
Animated.timing(closeAnimation.current, {
toValue: { x: 0, y: 0 },
duration: 100
}).start();
}
}
}, [lastScale]);
const onPinchGestureEvent = Animated.event([{ nativeEvent: { scale: scaleAnimation.current } }]);
useCallback(({ nativeEvent }) => {
scaleAnimation.current.setValue(nativeEvent.scale);
}, [lastScale]);
const onPinchHandlerStateChange = ({ nativeEvent }) => {
console.log('New Pinch Event', nativeEvent);
if (nativeEvent.oldState === State.ACTIVE) {
const newLastScale = lastScale * nativeEvent.scale;
setLastScale(newLastScale);
baseScale.current.setValue(newLastScale);
scaleAnimation.current.setValue(1);
}
};
return (
<PanGestureHandler maxPointers={2} avgTouches onHandlerStateChange={onPanHandlerStateChange}
minDist={10} onGestureEvent={onPanHandlerGesture} ref={panRef}>
<PinchGestureHandler ref={pinchRef} simultaneousHandlers={panRef}
onHandlerStateChange={onPinchHandlerStateChange} onGestureEvent={onPinchGestureEvent}>
<Animated.Image style={[
props.imageStyle,
{
transform: [
{ perspective: 1000 },
{
translateY: closeAnimation.current.y.interpolate({
inputRange: [-vh(25), vh(25)],
outputRange: [-vh(25), vh(25)],
extrapolate: "clamp"
})
},
{
translateX: closeAnimation.current.x.interpolate({
inputRange: [-vw(25), vw(25)],
outputRange: [-vw(10), vw(10)],
extrapolate: "clamp"
})
},
{
scale: scale.current.interpolate({
inputRange: [1, 2.5],
outputRange: [1, 2.5],
extrapolate: "clamp"
})
}
]
}
]} source={props.source} />
</PinchGestureHandler>
</PanGestureHandler>
);
};
ZoomableImage.defaultProps = {
imageStyle: {},
source: { uri: "" }
};
/********************** End ZoomableImage Function *******************/
export default ZoomableImage;
Could someone please help me?
according to me you need to include an other Animated.View (only acting as a wrapper) between your PanGH and PinchGH componant.
return (
<PanGestureHandler ...>
<Animated.View...>
<PinchGestureHandler ...>
<Animated.Image...>

Using the Animated API correctly in react native

I am fairly new to Animated API in react native. I went through a lot of tutorials for using animation API and it seems in every one of them the elements are positioned as absolute, is it necessary to position the element absolute ?
Also I made a piece of animation but it looks glitchy, I think the view after the textinput donot have absolute as position which might be causing the issue. Is it possible to do the animation I'm trying to while keeping the textinput position absolute but other elements positioned with flexbox?
Here is the code
handleFocus = () => {
console.log('starting animation');
this.setState({
isFocused: true
});
Animated.timing(this.isFromViewFocused, {
toValue: 1,
duration: 300
}).start();
}
handleBlur = () => {
console.log('Blurring');
this.setState({
isFocused: false
});
Animated.timing(this.isFromViewFocused, {
toValue: 0,
duration: 300
}).start();
}
render() {
const labelStyle = {
position: this.state.isFocused === true ? 'absolute' : 'relative',
alignItems: 'center',
width: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [DEVICE_WIDTH * 0.45, DEVICE_WIDTH]
}),
left: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [DEVICE_WIDTH * 0.03, 0]
}),
marginBottom: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [0, 80]
}),
top: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [10, 0]
}),
borderWidth: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [0, 5]
}),
borderColor: 'black',
paddingTop: this.state.isFocused === true ? 20 : 0
};
return (
<View style={styles.container}>
<ScrollView style={{ flex: 1 }} keyboardDismissMode='on-drag'>
<Animated.View
style={labelStyle}
>
<TextInput
onFocus={this.handleFocus}
onBlur={this.handleBlur}
style={{
borderColor: 'black',
borderWidth: 1,
width: '90%'
}}
>
<Text>Hey Text</Text>
</TextInput>
</Animated.View>
<Animated.View
style={[styles.LocationContainer,
{ marginTop: this.isFromViewFocused.interpolate({
inputRange: [0, 1],
outputRange: [20, 80]
})
}
]}>
Using absolute positioning combined with left, top, bottom, right is bad for performances. That's why your animations looks "glitchy".
You'd be better using transforms so your component stays relative and native optimizations can be made (same as CSS3 transforms).
Also, when using not native-optimizable properties (such as the one you use) you cannot set useNativeDriver to true. Which makes performances even worse.
To add with that, you cannot (or shouldn't) interpolate based on a boolean. AnimatedJS provides you with an Animated.Value class that aims to make it easier to interpolate stuff.
Here is a simpler example to Animated:
export class MyAnimatedComponent extends React.Component {
state = {
animatedValue: new Animated.Value(0);
}
focus = () => {
const { animatedValue } = this.state;
Animated.timing(animatedValue, {
duration: 280,
toValue: 1,
// This will make your animation glitch-free/performant.
useNativeDriver: true,
}).start();
}
blur = () => {
Animated.timing(animatedValue, {
duration: 140,
toValue: 0,
// This will make your animation glitch-free/performant.
useNativeDriver: true,
}).start();
}
render () {
const { animatedValue } = this.state;
const animatedStyles = {
transform: [
{
// Move the div by 120px to the left when value === 1
translateX: animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -120],
// Tells Animated to never go outside of the outputRange
extrapolate: 'clamp',
})
},
{
translateY: animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -50],
extrapolate: 'clamp',
})
}
]
}
return (
<View style={styles.wrapper}>
<Animated.View style={animatedStyles} onFocus={onFocus} onBlur={onBlur}>
I'm some content into an Animated div.
</Animated.View>
</View>
)
}
}

Categories