Reanimated does not work with animated code? Is there a way? - react-native

I am pretty new to React Native and reanimated so please bear with me if I cant explain this as I need to.
I have a FlatList that animates a single item using animted when clicked. This works great but the problem is I am trying to use it with code that uses reanimated and I get a lot of errors for some reason? I thought it was backwards compatible?
Because I need the animation to run only on a single item I can not use the reanimated way, it seems a lot harder to keep track of everything?
The code below the working code but if I change it to reanimated, no good.
Is there a way of converting the code to run properly when using reanimated or do I just have to start the animations again using reanimated animations from scratch?
The working code :-
import React, { useState, useEffect } from 'react';
import {
Text,
View,
StyleSheet,
FlatList,
TouchableOpacity,
Animated,
} from 'react-native';
import Constants from 'expo-constants';
const dummyData = [...Array(10)].map((_, i) => ({
title: `title ${i}`,
id: i,
}));
const App = () => {
const [Data, setData] = React.useState(dummyData);
const [activeItem, setActiveItem] = React.useState(null);
const animateValue = React.useRef(new Animated.Value(0)).current;
renderItem = ({ item, index }) => {
const animate = (index) => {
setActiveItem(index);
Animated.sequence([
Animated.spring(animateValue, {
toValue: 1,
}),
Animated.spring(animateValue, {
toValue: 0,
}),
]).start(() => console.log('animation finished'));
};
animationMap = animateValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.5],
});
return (
<TouchableOpacity onPress={(e) => animate(index)}>
{activeItem === index && (
<Animated.View
style={[styles.button, { transform: [{ scale: animationMap }] }]}>
<Text>{item.title}</Text>
</Animated.View>
)}
{activeItem !== index && (
<View style={styles.button}>
<Text>{item.title}</Text>
</View>
)}
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<FlatList
data={Data}
renderItem={renderItem}
keyExtractor={({ id }) => {
return id;
}}
extraData={activeItem}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
button: {
padding: 10,
borderWidth: 1,
alignItems: 'center',
backgroundColor: 'skyblue',
},
});
export default App;
If I change Animated from reanimated, it doesnt work?
import {
StatusBar,
Text,
View,
StyleSheet,
Image,
Dimensions,
TouchableOpacity,
Platform,
TouchableWithoutFeedback,
UIManager,
LayoutAnimation,
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
SlideInDown,
BounceOut,
BounceIn,
FadeIn,
FadeOut,
Easing,
withSpring,
withRepeat,
withTiming,
} from "react-native-reanimated";
or
import {
StatusBar,
Text,
View,
StyleSheet,
Image,
Dimensions,
TouchableOpacity,
Platform,
TouchableWithoutFeedback,
UIManager,
LayoutAnimation,
Animated,
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
SlideInDown,
BounceOut,
BounceIn,
FadeIn,
FadeOut,
Easing,
withSpring,
withRepeat,
withTiming,
} from "react-native-reanimated";

can you try using "useSharedValue" and set the value using
"withSequence" as below mentioned:
const App = () => {
const [Data, setData] = React.useState(dummyData);
const [activeItem, setActiveItem] = React.useState(null);
//const animateValue = React.useRef(new Animated.Value(0)).current;
const animateValue = useSharedValue(0);
renderItem = ({ item, index }) => {
const animate = (index) => {
setActiveItem(index);
animateValue.value = withSequence(withTiming(1), withTiming(0))
animationMap = animateValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.5],
});
return (
<TouchableOpacity onPress={(e) => animate(index)}>
{activeItem === index && (
<Animated.View
style={[styles.button, { transform: [{ scale: animationMap }] }]}>
<Text>{item.title}</Text>
</Animated.View>
)}
{activeItem !== index && (
<View style={styles.button}>
<Text>{item.title}</Text>
</View>
)}
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<FlatList
data={Data}
renderItem={renderItem}
keyExtractor={({ id }) => {
return id;
}}
extraData={activeItem}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
button: {
padding: 10,
borderWidth: 1,
alignItems: 'center',
backgroundColor: 'skyblue',
},
});
export default App;

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;

REACT NATIVE. How to dim the background of the bottomsheet?

I am trying to dim the background of this bottomSheet when it is activated or being rendered in the screen.
Also how do you make sure the bottomsheet disappears from the screen when the user touches the part of the screen that is not covered by the bottomSheet when it is active?
This is the code in app.js
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, View, ImageBackground, Text } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { TouchableOpacity } from 'react-native';
import BottomSheet from './components/BottomSheet';
import { useCallback, useRef } from 'react';
import Volcano from './Images/Volcano.jpg'
export default function App() {
const firstRef = useRef (null)
const onPress = useCallback (() => {
const isActive = firstRef?.current?.isActive1();
if (isActive) {
firstRef?.current?.scrollTo(35);
} else {
firstRef?.current?.scrollTo(-200);
}
})
return (
<GestureHandlerRootView style={{flex:1}}>
<ImageBackground source={Volcano} resizeMode='repeat' style={{
flex: 1,
width : '100%',
// flexDirection: 'column',
justifyContent: 'center',
}}>
<StatusBar style="auto" />
<TouchableOpacity style={{
height:50,
width: '10%',
backgroundColor:'green',
aspectRatio:1,
borderRadius:25,
opacity:.6,
marginLeft:360,
}} onPress={onPress}/>
<BottomSheet ref={firstRef}/>
</ImageBackground>
</GestureHandlerRootView>
);
};
This is the one in the bottomsheet.js
import { Dimensions, StyleSheet, Text, View } from 'react-native'
import React, { useCallback, useImperativeHandle } from 'react'
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { event, Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
const {height: SCREEN_HEIGHT} = Dimensions.get('window');
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 50;
const BottomSheet = React.forwardRef(({},ref) => {
const translateY = useSharedValue(35);
const active = useSharedValue()
const scrollTo = useCallback ((destination = Number) =>{
'worklet';
active.value = destination !== 35;
translateY.value = withSpring(destination, {damping:15});
}, []);
const isActive1 = useCallback (()=> {
return active.value;
},[])
useImperativeHandle(ref, () => ({scrollTo, isActive1}), [scrollTo, isActive1]);
const updatePan = useSharedValue({y:0});
const activateGesture = Gesture.Pan()
.onStart(() => {
updatePan.value = { y:translateY.value};
})
.onUpdate((e) => {
translateY.value = e.translationY + updatePan.value.y;
translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y);
})
.onEnd (() => {
if (translateY.value > -SCREEN_HEIGHT/3){scrollTo(35) ;
} else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y)
}
});
const rBottomSheetStyle = useAnimatedStyle (() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 100, MAX_TRANSLATE_Y],
[25, 5],
Extrapolate.CLAMP
);
return {
borderRadius,
transform: [{ translateY: translateY.value }],
};
});
return (
<GestureDetector gesture={activateGesture}>
<Animated.View
style= {[styles.bottomSheetContainer, rBottomSheetStyle]}
>
<View style={styles.line} />
</Animated.View>
</GestureDetector>
)
})
const styles = StyleSheet.create({
bottomSheetContainer: {
height: SCREEN_HEIGHT,
width:'100%',
backgroundColor: 'white',
position: 'absolute',
top: SCREEN_HEIGHT,
borderRadius: 25,
},
line:{
width: 75,
backgroundColor: 'grey',
height: 4,
alignSelf: 'center',
marginVertical: 15,
borderRadius:2,
}
})
export default BottomSheet
As you can see on the image the background is not dim when the bottomsheet is activatted.
[1]: https://i.stack.imgur.com/RdEzm.jpg
Please assist

Unable to Apply Layout Animation Using Reanimated API

I'm trying to apply layout animation to a FlatList upon adding and deleting a goal (list item) using the Reanimated API. I'm mainly following this tutorial from the Reanimated docs but I don't know why the animation is not applied when list items are added or removed. I should also inform that I'm only testing this on an Android device. Here is the code:
App.js (contains FlatList)
import { useState } from "react";
import { Button, FlatList, StyleSheet, View } from "react-native";
import GoalInput from "./components/GoalInput";
import GoalItem from "./components/GoalItem";
export default function App() {
const [goalList, setGoalList] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const styles = StyleSheet.create({
appContainer: {
paddingTop: 50,
},
});
function startAddGoalHandler() {
setIsModalOpen(true);
}
// spread existing goals and add new goal
function addGoalHandler(enteredGoalText) {
setGoalList((currentGoals) => [
...currentGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
function deleteGoalHandler(id) {
setGoalList((currentGoals) =>
currentGoals.filter((existingGoals) => existingGoals.id !== id)
);
}
return (
<View style={styles.appContainer}>
<Button
title='Add New Goal'
color='indigo'
onPress={startAddGoalHandler}
/>
{isModalOpen && (
<GoalInput
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
onAddGoal={addGoalHandler}
></GoalInput>
)}
<FlatList
keyExtractor={(item, index) => {
return item.id;
}}
data={goalList}
renderItem={(itemData) => {
return (
<GoalItem
onGoalDelete={deleteGoalHandler}
itemData={itemData}
/>
);
}}
/>
</View>
);
}
GoalItem.js (list item)
import React from "react";
import { Pressable, StyleSheet, Text } from "react-native";
import Animated, { Layout, LightSpeedInLeft, LightSpeedOutRight } from "react-native-reanimated";
const GoalItem = ({ itemData, onGoalDelete }) => {
const styles = StyleSheet.create({
goalCards: {
elevation: 20,
backgroundColor: "white",
shadowColor: "black",
height: 60,
marginHorizontal: 20,
marginVertical: 10,
borderRadius: 10,
},
});
return (
<Animated.View
style={styles.goalCards}
entering={LightSpeedInLeft}
exiting={LightSpeedOutRight}
layout={Layout.springify()}
>
<Pressable
style={{ padding: 20 }}
android_ripple={{ color: "#dddddd" }}
onPress={() => onGoalDelete(itemData.item.id)}
>
<Text style={{ textAlign: "center" }}>
{itemData.item.text}
</Text>
</Pressable>
</Animated.View>
);
};
export default GoalItem;
I've even tried replacing the FlatList with View but to no avail. I suspect that Reanimated isn't properly configured for my project, if I wrap some components with <Animated.View>...</Animated.View> (Animated from Reanimated and not the core react-native module) for example the child components will not show. Reanimated is installed through npm
Any help is appreciated, thanks!

Problems with automatic image carousel when data is dynamic from an api using react native

I am implementing an image carousel which has an automatic rotation. When I implement it with static data (for example: creating a constant with an array) it works just the way I want it to.
However when I am getting the data from an api using axios, the carrosuel has a wrong behavior. The wrong behavior is as follows:
Swipe to the second image on the carousel and before moving on to the third image, go back to the first image and then go to the third image, then go to the fourth image, go back to the first image, and then go to the first image. fourth, this behavior is repeated x times.
So I think the problem is when I use axios. I attach the code of the classes that intervene in the problem that I am currently presenting.
I am using react native 0.62 with hooks and axios
HomeScreen.js
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import CategoriesScreen from "./Categories/CategoriesScreen";
import { ScrollView } from "react-native-gesture-handler";
import Carousel from "./Banner/BannerOficial";
import { axiosClient } from "../../config/axios";
export default function HomeScreen({ navigation }) {
const [banners, setBanners] = useState([]);
useEffect(() => {
getBannersAPI();
}, []);
function getBannersAPI(){
axiosClient
.get("/service/banner_available")
.then(async function (response) {
setBanners(response.data);
})
.catch(function (error) {
console.log("Error cargando los banners: ", error);
});
}
return (
<View style={{ flex: 1 }}>
<ScrollView>
<Carousel data={banners} />
<CategoriesScreen navigation={navigation} />
</ScrollView>
</View>
);
}
Carousel.js
import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, Dimensions, FlatList, Animated } from 'react-native'
import CarouselItem from './BannerItem'
const { width, heigth } = Dimensions.get('window')
let flatList
function infiniteScroll(dataList){
const numberOfData = dataList.length
let scrollValue = 0, scrolled = 0
setInterval(function() {
scrolled ++
if(scrolled < numberOfData)
scrollValue = scrollValue + width
else{
scrollValue = 0
scrolled = 0
}
this.flatList.scrollToOffset({ animated: true, offset: scrollValue})
}, 3000)
}
const Carousel = ({ data }) => {
const scrollX = new Animated.Value(0)
let position = Animated.divide(scrollX, width)
const [dataList, setDataList] = useState(data)
useEffect(()=> {
setDataList(data)
infiniteScroll(dataList)
})
if (data && data.length) {
return (
<View>
<FlatList data={data}
ref = {(flatList) => {this.flatList = flatList}}
keyExtractor={(item, index) => 'key' + index}
horizontal
pagingEnabled
scrollEnabled
snapToAlignment="center"
scrollEventThrottle={16}
decelerationRate={"fast"}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return <CarouselItem item={item} />
}}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
)}
/>
<View style={styles.dotView}>
{data.map((_, i) => {
let opacity = position.interpolate({
inputRange: [i - 1, i, i + 1],
outputRange: [0.3, 1, 0.3],
extrapolate: 'clamp'
})
return (
<Animated.View
key={i}
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
/>
)
})}
</View>
</View>
)
}
console.log('Please provide Images')
return null
}
const styles = StyleSheet.create({
dotView: { flexDirection: 'row', justifyContent: 'center'}
})
export default Carousel
CarouselItem.js
import React from "react";
import { View, StyleSheet, Text, Image, Dimensions} from 'react-native';
const { width, height} = Dimensions.get('window')
const CarouselItem = ({item}) => {
return(
<View style={styles.cardView}>
<Image style={styles.image} source = {{ uri: item.imagePath}}/>
</View>
)
}
const styles = StyleSheet.create({
cardView:{
flex:1,
width: width -20,
height: height / 7,
backgroundColor: "white",
margin: 10,
borderRadius: 10,
shadowColor: "#000",
shadowOffset: {width: 0.5, height: 0.5},
shadowOpacity: 0.5,
shadowRadius: 3,
elevation: 5,
},
image: {
width: width-20,
height: height / 3,
borderRadius: 10
}
})
export default CarouselItem

React Native - Need to hide/show header with Animation on scroll regardless of scroll position

Currently I have this code:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
return (
<View style={container}>
<Animated.View
style={{
transform: [{
translateY: this.state.curY.interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
})
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);
When I start scrolling down header hides, and when I scroll up again appears. But header appears only when I get to the beginning of list.
And I need such solution: when I'm for example in the middle of the list and start scroll up - header should appear, and when started scroll down it hides again. So it should be independently from position.
It work this way in Facebook mobile app for example.
I found solution. Just need to use Animated.diffClamp.
Here is the final code. Maybe will be useful for someone:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
const headerDistance = Animated.diffClamp(this.state.curY, 0, 60).interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
});
return (
<View style={container}>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
<Animated.View
style={{
transform: [{
translateY: headerDistance
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);