React Native Animated API - combine translate and rotation - react-native

Encountered this issue recently when I work with react native animated API.
As the image shows, a card component is positioned at top left corner, its flip animation state is controlled by the rotateY value, moving animation is controlled by translateX and translateY values.
It seems the rotation pivot point always been set to the card's original position. After the card has been moved (changing the translateX and translateY value), the card flip rotation animates reference its original position.
It there a way to adjust the rotation pivot point? Alternatively, is there a way to animate component's position instead of translation? Thanks.

Got it working finally. Turns out you can animate the component position change without using the translate property, by adding a listener to the animated value and updating the component state accordingly:
in the constructor, setup card component initial position and cardPos animated value.
in the componentDidMount function, attach listeners to the animated values. when animated values change, update the component state.
in the render function set the component root value style to position:"absolute" and actual position sync to the values in component's state.
constructor(props){
super(props);
// set card initial position as component state
this.state = {
cardPosX: this.props.position.x,
cardPosY: this.props.position.y
};
this.flipAnimatedValue = new Animated.Value(
this.props.isFacingUp ? 180 : 0
);
this.flipAnimatedInterpolate = this.flipAnimatedValue.interpolate({
inputRange: [0, 90, 180],
outputRange: ["0deg", "90deg", "0deg"]
});
// create animated value for card Position X and Y
this.cardPosXAnimatedValue = new Animated.Value(this.props.position.x);
this.cardPosYAnimatedValue = new Animated.Value(this.props.position.y);
}
componentDidMount() {
// addListener for cardPos Animated Value
// when animated values change, update the component state
this.cardPosXAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosX: value });
});
this.cardPosYAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosY: value });
});
}
render(){
return (
<View
style={{
width: this.cardWidth,
height: this.cardHeight,
position: "absolute",
top: this.state.cardPosY, //card position sync with animated value
left: this.state.cardPosX
}}
>
... //child components
</View>
);
}

Related

How to make a React Native TextInput change Opacity just like TouchableOpacity?

My code looks something like this currently:
<View>
<TextInput placeholder='PlaceholderText'>
</TextInput>
</View>
I want to make a TextInput component that has an opacity animation on click (exactly like TouchableOpacity changes opacity on click).
I tried wrapping the TextInput inside TouchableOpacity, but it doesn't work since the touchable component surrounds text input. Is there a standard React Native or StyleSheet way of doing this or would I have to manually create an animation to mimic that effect?
Simply wrap your TextInput in a View element and animate the View's opacity color from the onFocs event of the TextInput. TextInput doesn't have the opacity attribute, therefore the additional view is required for your goal.
Following code may give you an idea how to solve it. Have to admit, that I haven't tested the code, it may contain some error, so use it carefully.
// First create an animated value to use for the view's opacity.
const textInputAnimOpacity = useRef(new Animated.Value(1.0)).current;
// create a method to set the opacity back to 1.0
const showTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 1.0, // value to reach
duration: 250 // time in ms
}).start();
};
// this animated method differs from the first one by (obviously the value 0.7)
//and the callback part that goes into the start()-method
const blurTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 0.7, // value to reach
duration: 250 // time in ms
}).start(({ finished }) => {
showTxtInput(); // Callback after finish, setting opacity back to 1.0
});
};
/*Set the opacity to the value, we want to animate*/
<View style={{opacity:textInputAnimOpacity}}>
/* call blurTxtInput to set the value to 0.7 and again to 1.0 once it reaches 0.7*/
<TextInput onPressIn={blurTxtInput} placeholder='PlaceholderText'>
</TextInput>
</View>
If you just want to set opacity, make your styles change using the onPressIn and onPressOut props:
const [pressed, setPressed] = useState(false);
// in render
<TextInput
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
style={pressed ? styles.textInputPressed : styles.textInput}
// ...
/>
If you need the changes to animate, you can do that with the built-in RN Animated component or react-native-reanimated, using the same props to trigger the animations.

Adjust the scrollview height dynamically at runtime

I am developing a react-native project.
I have a ScrollView in MyComponent, the content of ScrollView consists of :
a MySubComponent,
a Text
a Image component
All the content/data of above components have dynamic length or height. So, I would like adjust the content height of my ScrollView on the fly at runtime.
To achieve that my plan is to disable automaticallyAdjustContentInsets of the ScrollView as you can see from my below code snippet. Then, have a state variable to hold the latest value of contentInsetBottom. But I am not sure how can I calculate the height of child components so that I can call setContentInsetBottom(totalHeight) to update the content height of my ScrollView .
(I am pretty sure my plan will work if I know how to calculate the height of each child component of my ScrollView.)
Anyone can guide me a bit?
const MyComponent = ({myText, image}) => {
// I have a state of the contentInsetBottom for the ScrollView
const [contentInsetBottom, setContentInsetBottom] = useState(0);
// how can I get the height of each children component, sum them up & call setContentInsetBottom(totalHeight) here ?
return (
<ScrollView
automaticallyAdjustContentInsets={false}
contentInset={{top: 0, bottom: contentInsetBottom}}
style={styles.scrollView}
contentContainerStyle={styles.contentContainer}>
<MySubComponent />
<Text>{myText}</Text>
<Image source={{uri: image?.uri}}>
</ScrollView>)
}
Wrap all content inside the <ScrollView> in a <View>. Then use onLayout to get the height of that parent View.
const handleScrollContentLayout = (e) => {
const { height } = e.nativeEvent.layout
setScrollLayoutHeight(height)
}
...
<View onLayout={handleScrollContentLayout}>
{ /* scrollView content... */ }
</View>
Then you can use the scrollLayoutHeight as per your needs to set the height at runtime.

React Native animate position (Left & Right) using useNativeDriver

Is it possible to extend ReactNative to allow animation of an element's position using the native driver.
Whilst you can animate via translateX, in this case, the size of the element is to be reduced adding by changing both its left and right style parameters.
Neither left, right, marginLeft, marginRight, paddingLeft nor paddingRight are supported for native animations. Is there a way around this or some ingenious idea involving scaling that won't distort the element.
export class ViewScreen extends React.Component {
constructor(props) {
super(props);
this.state= {
scrollY: new Animated.Value(0)
}
}
render() {
var VSStickyElementsMargin = this.state.scrollY.interpolate({
inputRange: [0, 44],
outputRange: [0, 4]
});
return (
<Animated.ScrollView
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.state.scrollY } }}],
{ useNativeDriver: true }
)}>
<Animated.View style={[
{left: VSStickyElementsMargin},
{right: VSStickyElementsMargin}
]}>
<Text>I am content that is not to become distorted</Text>
</Animated.View>
</Animated.ScrollView>
)
}
}
There is no way to use native driver on properties that are not transforms or opacity.
If I understand what you are trying to achieve correctly you should use use translateX to move the element outwith its parent and animate it back into view when you need to, use overflow hidden on the parent.
Alternatively if this is not the effect you want try looking at the layoutanimation api as you can get better performance using it to drive values than animating non transform/opacity properties.

Title animations on screen transition in custom Header component?

I have my own Header component that has a search field and some other features unique to my app. I simply define it on the navigationOptions for each screen.
SomeScreen.navigationOptions = (props) => ({
header: <Header {... props} />,
});
What I want to do though is animate the title inside the component itself as it moves from screen to screen? In general how can I accomplish this with a custom header? I've looked at the react-navigation-stacks implementation of Header but I can't make much sense of it.
Depending on your version of stack navigator, you'll need to check the props you receive. First of all, pass a function returning an element to the header option so that you receive the props:
SomeScreen.navigationOptions = {
header: props => <Header {...props} />
};
If you're on react-navigation-stack#2.x (alpha), you get a prop called scene. It's an object containing a progress property which has 3 properties which are reanimated nodes.
current: represents the progress of the screen that the header, it ranges from 0 to 1, where 0 means the screen is fully hidden, and 1 means the screen is fully shown.
next: similar to current, but for the next screen in the stack. can be undefined when there's no next screen
previous: similar to current, but for the previous screen in the stack. can be undefined when there's no previous screen
You can interpolate on these values to animate your view. For example, say you want to animate the opacity to start from 0.5 and to 1 as screen comes in:
import Animated from "react-native-reanimated";
// ...
<Animated.Text
style={{
opacity: Animated.interpolate(scene.progress.current, {
inputRange: [0, 1],
outputRange: [0.5, 1]
})
}}
/>;
If you're on react-navigation-stack#1.x, you get a prop called position. It's an animated node which represents the current position of the stack (ranges from 0 to current index). You can interpolate on it to animate your header. It can be a bit tricky, so you'll have to play around.
For example, say you want to animate the opacity to start from 0.5 and to 1 as screen comes in:
NOTE: I haven't tested this one so the code might be wrong. So you'll have to play around with it.
import { Animated } from "react-native";
// ...
<Animated.Text
style={{
opacity: position.interpolate({
inputRange: [index - 1, index],
outputRange: [0.5, 1]
})
}}
/>;

How can I implement animation in my flatlist?

I am using Flatlist in my rn project and when I push new data into my flatlist, my item 1 will automatically move from position A to position B. But my question is I don't want it to just change the position, I want to use animation to move my item(from position A to position B). How can I implement that? Thank you!
Please check the demo picture and video from the link down below:
https://photos.app.goo.gl/WypswNyA38A2EAPQA
https://photos.app.goo.gl/Ev1RYMduDj7mxrHn7
You can use Animated component to do the animation. As per your attached video, 2 steps animation comes into play, one which pushes the items up in the list and another one which increases the opacity of the list item. A simple approach would be to add the list item with height 0 and increase the height to desired height using animation, this will complete the first step. Once the first step is completed, control the opacity to go from 0 to 1.
Next, you need to start the animation when the list item is added to the list, componentDidMount is the right place to do so. Please consider the following component which does the above steps.
import React from 'react';
import { Animated } from 'react-native';
class AnimatedListItem extends React.Component {
constructor(...props) {
super(...props);
this.state = {
height: new Animated.Value(0),
opacity: new Animated.Value(0)
};
}
componentDidMount() {
Animated.sequence([
Animated.timing(
this.state.height,
{
toValue: this.props.height,
duration: this.props.duration || 1000
}
),
Animated.timing(
this.state.opacity,
{
toValue: 1,
duration: this.props.duration || 1000
}
)
]).start();
}
render() {
const { height, opacity } = this.state;
return (
<Animated.View
style={{
...this.props.style,
height: height,
opacity: opacity
}}
>
{this.props.children}
</Animated.View>
);
}
}
export default AnimatedListItem;
In the above snippet, two animations are passed to Animated.sequence([...]) method to animate one after the other.
You can now use the above component in the renderItem method like
renderItem = () => {
return (
<AnimatedListItem height={50} duration={800} style={...}>
/* render actual component here */
</AnimatedListItem>
);
}
Hope this will help!
Note: This is a bare minimum example to achieve what you are looking for.