An re-animation (1.13) is used to toggle accordion in React Native app. When accordion is open or close, an arrow is up or down. interpolate is used to animate when arrow from up to down or vise verse.
import Animated, { useValue, interpolate, Easing, useCode, State, greaterThan, lessThan } from "react-native-reanimated";
const animatedController = useValue(0); //<<==animated value between [0, 1]
const arrowAngle = interpolate(animatedController, { //<<==interpolate causes error of node cannot be cast to number
inputRange: [0, 0.5, 1],
outputRange: ['0rad', `${0.5*Math.PI}rad`,`${Math.PI}rad`],
extrapolate: Extrapolate.CLAMP,
});
return (
<>
<TouchableWithoutFeedback onPress={() => setOpen(!open)}>
<View style={styles.titleContainer}>
<Text>{title}</Text>
<Animated.View style={{ transform: [{ rotateZ: arrowAngle }] }}> //<<==arrowAngle call here
<Icon name="chevron-down-outline" size={20} />
</Animated.View>
</View>
</TouchableWithoutFeedback>
</>
)
However the method above causes the error of node cannot be cast to number as below. This interpolate seems very simple. What's wrong here?
The output range shall be a number like this:
const arrowAngle = interpolate(animatedController, {
inputRange: [0, 0.5, 1],
outputRange: [0.01, 1/2*Math.PI,Math.PI], //<<==shall be digital
extrapolate: Extrapolate.CLAMP,
});
Related
I have a screen that show chat messages, and I make the item chat able to swipe to reply like FB Messenger. I'm using Swipeable component from react-native-gesture-handler. But I have a troble: whenever I swipe fastly, the item will swipe to outside the screen like this: https://i.stack.imgur.com/uIEWN.png
How can I fix it?
My code for Swipeable Item:
<Swipeable
failOffsetX={
!isMyMessage ? [0, SCREEN_WIDTH / 3] : [-SCREEN_WIDTH / 2, 0]
}
ref={swipeableRef}
friction={2}
enableTrackpadTwoFingerGesture
renderLeftActions={renderSwipeableActions }
renderRightActions={renderSwipeableActions }
onSwipeableClose={onSwipeableClose}
onSwipeableOpen={() => {}} />
And this one for render UI when swipe action activate :
const renderSwipeableActions = (
_progress: Animated.AnimatedInterpolation,
dragX: Animated.AnimatedInterpolation,
) => {
const transformLeft = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
extrapolate: 'clamp',
});
const transformRight = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [1, 0, 0, -20],
extrapolate: 'clamp',
});
return (
<RectButton
style={{
flex: 1,
alignItems: !isMyMessage ? 'flex-start' : 'flex-end',
}}>
<Animated.View
style={[
{
transform: [
{translateX: !isMyMessage ? transformLeft : transformRight},
],
},
]}>
Below code is to render a Touchable button with Transform Animation.
const { scrollY, headerScrollDistance } = this.state;
const profileImageTranslateX = scrollY.interpolate({
inputRange: [0, headerScrollDistance],
outputRange: [0, -(ScreenWidth /2) + 32],
extrapolate: 'clamp',
});
const profileImageTranslateY = scrollY.interpolate({
inputRange: [0, headerScrollDistance],
outputRange: [0, -11],
extrapolate: 'clamp',
});
const profileImageScale = scrollY.interpolate({
inputRange: [0, headerScrollDistance / 2, headerScrollDistance],
outputRange: [1, 0.8, 0.6],
extrapolate: 'clamp',
});
return (
<Animated.View
style={[
Styles.animatedView.profileStyle,
{
transform: [
{ translateX: profileImageTranslateX },
{ translateY: profileImageTranslateY },
{ scale: profileImageScale }
]
}
]}
>
<TouchableOpacity activeOpacity={0.5} onPress={() => this.props.history.push('./profilePhotoChanger')}>
<ImageComp profileImageUrl={profileimageurl} imageStyle={Styles.homePageImageStyle} />
</TouchableOpacity>
</Animated.View>
);
As page scrolls, Animation applies to Touchable button. Button is working as expected when transform animation is not applied. But not working when animation is applied. If page comes back to it's normal state(i.e scrolling back) then button works as expected.
Is it normal behaviour in react-native that TouchableOpacity's onPress wont't work when animation applied? or is something wrong with my code?
You can try one of the following option if it works for you
1- import { TouchableOpacity } from 'react-native-gesture-handler';
2- Change height of Animated.View which contain TouchableOpacity (to
fit size of TouchableOpacity)
3- By moving <Animated.View> inside TouchableOpacity
Look like there is open discussion on Touchableopacity not working inside Animated.View
App with scroll
In the image I have drawn you can see two areas, the yellow one is a ScrollView, and the red one is a flatlist.
When I scroll from the red zone, I want the tabs to go up to the header, and once they touch the header, start scrolling the red zone of FlatList.
To do this, when the tabs touch the header I set the scrollEnabled from the yellow zone to false, the problem is that the red zone doesn't scroll until I stop pressing and press again.
The behavior I want it to have is similar to the instagram profile, where there are some tabs and then a list of photos, when the tabs touch the header, you can continue scrolling from the images.
I'll add a few options
First one is a snack, which again uses parent and child scrollviews
Collapsible Header Tabs Snack
Problems: This has issues on Android. the scroll stutters on android while both parent and child scrollviews are scrolling.
Second is github repo React-Native-Collapsing-TabView
Problems: if one of the tab is scrolled and you go to another tab then there will white space on top.issue
Using Native Base.
import React, {Component} from "react";
import {Animated, Dimensions, Platform, Text, TouchableOpacity, View} from "react-native";
import {Body, Header, List, ListItem as Item, ScrollableTab, Tab, TabHeading, Tabs, Title} from "native-base";
import LinearGradient from "react-native-linear-gradient";
const {width: SCREEN_WIDTH} = Dimensions.get("window");
const IMAGE_HEIGHT = 250;
const HEADER_HEIGHT = Platform.OS === "ios" ? 64 : 50;
const SCROLL_HEIGHT = IMAGE_HEIGHT - HEADER_HEIGHT;
const THEME_COLOR = "rgba(85,186,255, 1)";
const FADED_THEME_COLOR = "rgba(85,186,255, 0.8)";
export default class ParallaxDemo extends Component {
nScroll = new Animated.Value(0);
scroll = new Animated.Value(0);
textColor = this.scroll.interpolate({
inputRange: [0, SCROLL_HEIGHT / 5, SCROLL_HEIGHT],
outputRange: [THEME_COLOR, FADED_THEME_COLOR, "white"],
extrapolate: "clamp"
});
tabBg = this.scroll.interpolate({
inputRange: [0, SCROLL_HEIGHT],
outputRange: ["white", THEME_COLOR],
extrapolate: "clamp"
});
tabY = this.nScroll.interpolate({
inputRange: [0, SCROLL_HEIGHT, SCROLL_HEIGHT + 1],
outputRange: [0, 0, 1]
});
headerBg = this.scroll.interpolate({
inputRange: [0, SCROLL_HEIGHT, SCROLL_HEIGHT + 1],
outputRange: ["transparent", "transparent", THEME_COLOR],
extrapolate: "clamp"
});
imgScale = this.nScroll.interpolate({
inputRange: [-25, 0],
outputRange: [1.1, 1],
extrapolateRight: "clamp"
});
imgOpacity = this.nScroll.interpolate({
inputRange: [0, SCROLL_HEIGHT],
outputRange: [1, 0],
});
tabContent = (x, i) => <View style={{height: this.state.height}}>
<List onLayout={({nativeEvent: {layout: {height}}}) => {
this.heights[i] = height;
if (this.state.activeTab === i) this.setState({height})
}}>
{new Array(x).fill(null).map((_, i) => <Item key={i}><Text>Item {i}</Text></Item>)}
</List></View>;
heights = [500, 500];
state = {
activeTab: 0,
height: 500
};
constructor(props) {
super(props);
this.nScroll.addListener(Animated.event([{value: this.scroll}], {useNativeDriver: false}));
}
render() {
return (
<View>
<Animated.View style={{position: "absolute", width: "100%", backgroundColor: this.headerBg, zIndex: 1}}>
<Header style={{backgroundColor: "transparent"}} hasTabs>
<Body>
<Title>
<Animated.Text style={{color: this.textColor, fontWeight: "bold"}}>
Tab Parallax
</Animated.Text>
</Title>
</Body>
</Header>
</Animated.View>
<Animated.ScrollView
scrollEventThrottle={5}
showsVerticalScrollIndicator={false}
onScroll={Animated.event([{nativeEvent: {contentOffset: {y: this.nScroll}}}], {useNativeDriver: true})}
style={{zIndex: 0}}>
<Animated.View style={{
transform: [{translateY: Animated.multiply(this.nScroll, 0.65)}, {scale: this.imgScale}],
backgroundColor: THEME_COLOR
}}>
<Animated.Image
source={{uri: "https://upload.wikimedia.org/wikipedia/commons/c/c5/Moraine_Lake_17092005.jpg"}}
style={{height: IMAGE_HEIGHT, width: "100%", opacity: this.imgOpacity}}>
{/*gradient*/}
{/* <LinearGradient
colors={["rgba(255,255,255,0.9)", "rgba(255,255,255,0.35)", "rgba(255,255,255,0)"]}
locations={[0, 0.25, 1]}
style={{position: "absolute", height: "100%", width: "100%"}}/> */}
</Animated.Image>
</Animated.View>
<Tabs
prerenderingSiblingsNumber={3}
onChangeTab={({i}) => {
this.setState({height: this.heights[i], activeTab: i})
}}
renderTabBar={(props) => <Animated.View
style={{transform: [{translateY: this.tabY}], zIndex: 1, width: "100%", backgroundColor: "white"}}>
<ScrollableTab {...props}
renderTab={(name, page, active, onPress, onLayout) => (
<TouchableOpacity key={page}
onPress={() => onPress(page)}
onLayout={onLayout}
activeOpacity={0.4}>
<Animated.View
style={{
flex: 1,
height: 100,
backgroundColor: this.tabBg
}}>
<TabHeading scrollable
style={{
backgroundColor: "transparent",
width: SCREEN_WIDTH / 2
}}
active={active}>
<Animated.Text style={{
fontWeight: active ? "bold" : "normal",
color: this.textColor,
fontSize: 14
}}>
{name}
</Animated.Text>
</TabHeading>
</Animated.View>
</TouchableOpacity>
)}
underlineStyle={{backgroundColor: this.textColor}}/>
</Animated.View>
}>
<Tab heading="Tab 1">
{this.tabContent(30, 0)}
</Tab>
<Tab heading="Tab 2">
{this.tabContent(15, 1)}
</Tab>
</Tabs>
</Animated.ScrollView>
</View>
)
}
}
Problems: All screens scrolling because of same offset
Fourth is a module Sticky parallax header you can use tabbed header from here
Problems: Since its module not a lot of room for customization, but there are enough. And also when i was using it there were some issues like all screens scrolling because of same offset which might have been solved now.
So basically speaking all of them have some problems that you will have to solve later on.
But I would recommend the 4th option using the sticky-parallax-header by netguru.
I am trying to pass style to children, my code is :
render() {
const RotateData = this.RotateValueHolder.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
Object.assign(this.props.children.props.style,{'transform': [{'rotate': RotateData}] });
return (
<View style={{flex:1}}>
{this.props.children}
</View>
);
}
I getting this error :
Invariant Violation: Transform with key of "rotate" must be a string:
{"rotate":"0deg"}
Rotate can be only used by the animated component, so you need to apply the style to Animated.View or any Animated element. so try to use it with animated component.
Seems like I did everything in the documentation and referenced other examples.
Trying to animate a text component rotation:
this.state = {
rotateAnimation: new Animated.Value(0),
};
spin = () => {
this.state.rotateAnimation.setValue(0);
Animated.timing(this.state.rotateAnimation, {
toValue: 1,
duration: 3000,
easing: Easing.linear
}).start((animation) => {
if (animation.finished) {
this.spin();
}
});
};
render() {
return (
<Content>
<View>
<Text style={{
transform: [
{
rotate:
this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
}
]
} ]}>{this.FAIcons.circleONotch}</Text>
</View>
</Content>
);
}
This works fine if I manually enter in any degree i.e rotate: "90deg"
However, when I use interpolate(), I get this error: Transform with key of "rotate" must be a string: {"rotate":"0deg"}
Seems like Interpolate is not returning a string. I tried to typecast it using "toString()" but then I get this error: Rotate transform must be expressed in degrees (deg) or radians (rad): {"rotate":"[object Object]"}
I followed this documentation: https://facebook.github.io/react-native/docs/animations.html
And referenced this example: https://gist.github.com/levynir/5962de39879a0b8eb1a2fd77ccedb2d8
What am I doing wrong here?
**** EDIT ****
Thanks to #Guilherme Cronemberger for pointing me in the right direction, you need to create the component like this.
render() {
const StyledAnimatedText = Animated.createAnimatedComponent(Text);
}
Then utilize it like this:
return (
<StyledAnimatedText
style={{
fontFamily: 'FontAwesome',
backgroundColor: 'transparent',
transform: [{
rotate: this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
},
{ perspective: 1000 }]
}}>
{this.FAIcons.circleONotch}
</StyledAnimatedText>
)
Interpolate is a function which results are used only in declared "Animated" classes, so you'd add "Animated." to your Text class.
render() {
var rotateProp = this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
console.log(rotateProp) //just to check if it's returning what you want
return (
<Content>
<View>
<Animated.Text style={{
transform: [
{
rotate: rotateProp
}
]
} ]}>{this.FAIcons.circleONotch}</Animated.Text>
</View>
</Content>
);
}
I had the same problem
Just use <Animated.Text> instead of <Text />
I had the same issue
I fixed it by using <Animated.SomeComponent> instead of <SomeComponent>
As an example if you want to animate View component :
import { Animated, View } from 'react-native';
<Animated.View>
<ChildComponents/>
</Animated.View>
Thanks.
I had the same problem and I fix it by doing this.Convert PI in degree = (PI = 180deg) & (2 * PI= 360geg),
The answer is :
{ rotate: progress.interpolate(
{
inputRange: [0.5, 1],
outputRange: ['180deg', '360deg'],
}
),
},