I use animated.ScrollView and animated.View to move header and tabs to the top of the screen by animation when the user scrolls up the page.
everything is ok until here.
I want to use FlatList With sticky headers in one of the tabs.
when I use animated.ScrollView, sticky headers of FlatList not work!
and when in replace animated.ScrollView to animated.View this resolve but header and tabs cant move to top of the screen!
my code:
render() {
// Because of content inset the scroll value will be negative on iOS so bring
// it back to 0.
const scrollY = Animated.add(
this.state.scrollY,
Platform.OS === 'ios' ? HEADER_MAX_HEIGHT : 0,
);
const headerTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [0, -HEADER_SCROLL_DISTANCE],
extrapolate: 'clamp',
});
const backBtn = scrollY.interpolate({
inputRange: [0,HEADER_SCROLL_DISTANCE / 4, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
outputRange: [0, 0 , 0, 1],
extrapolate: 'clamp',
});
const imageOpacity = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
outputRange: [1, 1, 0],
extrapolate: 'clamp',
});
const imageTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [0, 100],
extrapolate: 'clamp',
});
const titleScale = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
outputRange: [1, 1, 0.6],
extrapolate: 'clamp',
});
const titleTranslate = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
outputRange: [0, 0, -8],
extrapolate: 'clamp',
});
const tabY = scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE, HEADER_SCROLL_DISTANCE + 1],
outputRange: [0, 0, 1]});
return (
<View style={styles.fill}>
<StatusBar
barStyle="light-content"
backgroundColor="#333f5b"
/>
<Animated.ScrollView
style={styles.fill}
scrollEventThrottle={1}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
{ useNativeDriver: true },
)}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={() => {
this.get_detail();
this.get_Comment(this.state.type,this.state.id);
this.setState({ refreshing: true,
loadingComment:true,
});
}}
// Android offset for RefreshControl
progressViewOffset={HEADER_MAX_HEIGHT}
/>
}
// iOS offset for RefreshControl
contentInset={{
top: HEADER_MAX_HEIGHT,
}}
contentOffset={{
y: -HEADER_MAX_HEIGHT,
}}
>
<Tabs renderTabBar={(props) => <Animated.View
style={[{
transform: [{translateY: tabY}],
zIndex: 1,
width: "100%",
backgroundColor: COLOR,
marginTop:HEADER_MAX_HEIGHT ,
}, Platform.OS === "ios" ? {paddingTop: 20} : null]}>
<ScrollableTab {...props} underlineStyle={{backgroundColor: "white"}}/>
</Animated.View>
}>
<Tab heading="MainTab" {...TAB_PROPS}>
{this._renderMainTab()}
</Tab>
<Tab heading="ListTab" {...TAB_PROPS}>
{this._renderListTab()}
</Tab>
<Tab heading="CommentTab" {...TAB_PROPS}>
{this._renderCommentTab()}
</Tab>
</Tabs>
</Animated.ScrollView>
<Animated.View
pointerEvents="none"
style={[
styles.header,
{ transform: [{ translateY: headerTranslate }] },
]}
>
<Animated.Image
style={[
styles.backgroundImage,
{
opacity: imageOpacity,
transform: [{ translateY: imageTranslate }],
},
]}
source={{uri:this.state.top_image}}
/>
</Animated.View>
<Animated.View
style={[
styles.bar,
{
transform: [
{ scale: titleScale },
{ translateY: titleTranslate },
],
},
]}
>
<Text style={styles.title}>{this.state.title}</Text>
</Animated.View>
<Animated.View
style={[
styles.backBtn,
{
opacity: backBtn,
},
]}
>
<Button transparent onPress={this.back}>
<Icon reverse type="MaterialIcons" name="arrow-back" style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}],color:"#fff"}}/>
</Button>
</Animated.View>
</View>);}}
and FlatList is in _renderListTab() :
<FlatList style={{backgroundColor: '#eee'}}
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={item => item.title+item.id}
stickyHeaderIndices={this.props.sticky}/>
how can fix it?!
are there any way to use FlatList with sticky headers in tabs that in animated.ScrollView?
Related
here is my code.
const actionButtonsAnimated = new Animated.Value(0);
const animated = new Animated.Value(255);
const animateTrendingCardSheet = () => {
Animated.timing(animated, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}).start();
Animated.timing(actionButtonsAnimated, {
toValue: -180,
duration: 1000,
useNativeDriver: true,
}).start();
};
<Animated.View
style={[
{
transform: [{ translateY: actionButtonsAnimated }],
},
]}
>
<MainActionButtons />
</Animated.View>
<Animated.View
style={[
{
transform: [{ translateY: animated }],
width: "100%",
position: "absolute",
bottom: 0,
},
]}
>
<View style={styles.trendingCards}>
<Text h5 center color={colors.trendingText}>
Trending in your area...
</Text>
<View style={styles.flatlistWrapper}>
<FlatList
horizontal={true}
data={trendingCards}
renderItem={({ item }) => <TrendingCardComponent card={item} />}
/>
</View>
</View>
</Animated.View>
so if i call the animateTrendingCardSheet function inside useEffect like this.
useEffect(() => {
animateTrendingCardSheet()
}, [])
it works as expected but once i put it in a condition that it should be called after the API call has been finished it does not work at all if i again save the file it hot reload animation works
useEffect(() => {
if (loadTrendingCard) {
animateTrendingCardSheet();
}
}, [loadTrendingCard]);
Your issue is that after the first call to animateTrendingCardSheet the toValue that you are animating to is the current value of your Animated variables; so it looks like nothing is happening. You can counteract this by resetting your animation variables before calling your animation function:
import * as React from 'react';
import {
Text,
View,
StyleSheet,
Animated,
TouchableOpacity,
FlatList,
Button
} from 'react-native';
import Constants from 'expo-constants';
const MainActionButtons = () => {
return (
<View
style={{
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
}}>
<TouchableOpacity>Btn 1</TouchableOpacity>
<TouchableOpacity>Btn 2</TouchableOpacity>
<TouchableOpacity>Btn 3</TouchableOpacity>
<TouchableOpacity>Btn 4</TouchableOpacity>
</View>
);
};
const TrendingCardComponent = ({ card }) => {
return (
<View style={{ width: '100%' }}>
<Text>{card.title}</Text>
<Text>{card.message}</Text>
</View>
);
};
const trendingCards = [
{ title: 'A Cool Card', message: 'A cool message' },
{ title: 'Card 1', message: 'A cool message' },
{ title: 'A Cool Card', message: 'A cool message' },
{ title: 'A Cool Card', message: 'A cool message' },
];
const initialActionButton = 0;
const initialAnimated = 255;
export default function App() {
const actionButtonsAnimated = new Animated.Value(initialActionButton);
const animated = new Animated.Value(initialAnimated);
const opacity = new Animated.Value(1)
const onApiCall = ()=>{
// set opacity to 0
Animated.timing(opacity,{toValue:0,duration:500}).start(()=>{
// when view is invisible do resets
animated.setValue(initialAnimated)
actionButtonsAnimated.setValue(initialActionButton)
Animated.timing(opacity,{toValue:1,duration:500}).start()
animateTrendingCardSheet()
})
}
const animateTrendingCardSheet = () => {
Animated.timing(animated, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}).start();
Animated.timing(actionButtonsAnimated, {
toValue: -180 ,
duration: 1000,
useNativeDriver: true,
}).start();
};
// React.useEffect(() => {
// animateTrendingCardSheet();
// }, []);
return (
<View style={styles.container}>
<Button title="Simulate API call" onPress={onApiCall}/>
<Animated.View
style={[
{
transform: [{ translateY: actionButtonsAnimated }],
opacity
},
]}>
<MainActionButtons />
</Animated.View>
<Animated.View
style={[
{
transform: [{ translateY: animated }],
width: '100%',
position: 'absolute',
bottom: 0,
opacity
},
]}>
<View style={styles.trendingCards}>
<Text>Trending in your area...</Text>
<View style={styles.flatlistWrapper}>
<FlatList
style={{ flex: 1 }}
horizontal={true}
data={trendingCards}
renderItem={({ item }) => <TrendingCardComponent card={item} />}
/>
</View>
</View>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
flatlistWrapper: {
width: '100%',
height: 200,
},
});
Demo
The above answer is good but it does not solve my problem, its all due to a stupid mistake, the issue was i was setting the state before calling the animation call function, as the state changes and it interfere the animation i had to use useRef like this.
const actionButtonsAnimated = useRef(new Animated.Value(initialActionButton)).current;
const animated = useRef(new Animated.Value(initialAnimated)).current;
it reference the state and animation works as expected.
Problem:
In my react native app I have created an animation like tinder cards. It is working correctly in Ios but in Android Animated view which has panResponder and transform even does not render in the view . This is how I have organized my code.
const TinderCardAnimation = ({ theme, data, onHeartClick }) => {
const { width, height } = useWindowDimensions();
const [currentIndex, setCurrentIndex] = useState(0);
const pan = useRef(new Animated.ValueXY()).current;
const rotate = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: ['-10deg', '0deg', '10deg'],
extrapolate: 'clamp'
})
const rotateAndTranslate = {
transform: [{
rotate: rotate
},
...pan.getTranslateTransform()
]
}
const likeOpacity = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp'
})
const nopeOpacity = pan.x.interpolate({
inputRange: [-width / 2, 0, width / 2],
outputRange: [1, 0, 0],
extrapolate: 'clamp'
})
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) =>
true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) =>
true,
onPanResponderTerminationRequest: (evt, gestureState) =>
true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value
});
},
onPanResponderMove: (evt, gestureState) => {
pan.setValue({
x: gestureState.dx,
y: gestureState.dy
})
},
onPanResponderRelease: () => {
pan.flattenOffset();
},
onShouldBlockNativeResponder: (evt, gestureState) => {
// Returns whether this component should block native components from becoming the JS
// responder. Returns true by default. Is currently only supported on android.
return true;
}
})
).current;
return <View style={{ backgroundColor: '#ffffff' }}>{data?.map((item, index) => {
if (index < currentIndex) {
return null
} else if (index == currentIndex) {
return (<Animated.View {...panResponder.panHandlers} key={index} style={[rotateAndTranslate, { height: height / 5, width: width, padding: 10, position: 'absolute' }]}>
<Animated.View
style={{
opacity: likeOpacity,
transform: [{ rotate: "-30deg" }],
position: "absolute",
top: 50,
left: 40,
zIndex: 1000
}}
>
<TouchableOpacity >
<Image style={{ marginTop: height / 4 }} source={require("_assets/images/XCircleb.png")} />
</TouchableOpacity>
</Animated.View>
<Animated.View
style={{
opacity: nopeOpacity,
transform: [{ rotate: "30deg" }],
position: "absolute",
top: 50,
right: 40,
zIndex: 1000
}}
>
<TouchableOpacity onPress={() => onHeartClick(item?.id)}>
<Image style={{ marginTop: height / 4 }} source={require("_assets/images/HCircleb.png")} />
</TouchableOpacity>
</Animated.View>
<ProfileCard key={index} profile={item} />
</Animated.View>)
} else {
return (<Animated.View key={index} style={{ height: height / 5, width: width, padding: 10, position: 'absolute' }}>
<ProfileCard key={index} profile={item} />
</Animated.View>)
}
}).reverse()}
</View >
}
export default withTheme(TinderCardAnimation);
I tried a lot to make it work in Android. But I was unable to do so. Can someone help me to solve this issue? Thank you
I am using react-native-animated-charts to render a chart. However, I am having difficulty reading x, and y values of the moveable chart dot. The documentation mentions that useChartData helper function gives access to this information. However, I am unsure how to use (or even where to use or initialize this function).
EDIT: I have added the code below
import React, {useEffect, useState, useRef} from 'react';
import {Text, Dimensions, View, TouchableHighlight, StyleSheet} from 'react-native';
import {
ChartDot,
ChartPath,
ChartPathProvider,
ChartYLabel,
ChartXLabel,
useChartData,
monotoneCubicInterpolation,
} from '#rainbow-me/animated-charts';
import {runOnJS} from 'react-native-reanimated';
import Card2View from '..//Card2View/Card2View';
import styles from './styles';
export const {width: SIZE, height} = Dimensions.get('window');
const TABLE_ITEM_OFFSET = 10;
const TABLE_ITEM_MARGIN = TABLE_ITEM_OFFSET * 2;
const SCREEN_WIDTH = SIZE < height ? SIZE : height;
export const data = [
{x: 1453075200, y: 1.47},
{x: 1453161600, y: 1.37},
{x: 1453248000, y: 1.53},
{x: 1453334400, y: 1.54},
{x: 1453420800, y: 1.52},
{x: 1453507200, y: 2.03},
{x: 1453593600, y: 2.1},
{x: 1453680000, y: 2.5},
{x: 1453766400, y: 2.3},
{x: 1453852800, y: 2.42},
{x: 1453939200, y: 2.55},
{x: 1454025600, y: 2.41},
{x: 1454112000, y: 2.43},
{x: 1454198400, y: 2.2},
];
const points = monotoneCubicInterpolation({data, range: 40});
const LineChartView1 = ({priceData}) => {
const [activeChart, setActiveChart] = useState(0)
const lineChartTables = ['1D', '1W', '1M', '3M', '1Y', 'ALL'];
const output = useChartData()
console.log(output);
const getX = value => {
'worklet';
// console.log(runOnJS(useChartData("state")));
if (value === '') {
return '';
}
return `$ ${value.toLocaleString('en-US', {
currency: 'USD',
})}`;
};
const getY = value => {
'worklet';
// console.log(runOnJS(useChartData("state")));
if (value === '') {
return '';
}
const date = new Date(Number(value * 1000));
const s = date.getSeconds();
const m = date.getMinutes();
const h = date.getHours();
const d = date.getDate();
const n = date.getMonth();
const y = date.getFullYear();
return `${y}-${n}-${d} ${h}:${m}:${s}`;
};
renderTable = (item, index) => (
<TouchableHighlight
onPress={() => setActiveChart(index)}
underlayColor="rgba(73,182,77,1,0.9)"
key={index}
style={
activeChart == index
? {
justifyContent: 'center',
backgroundColor: '#617180',
borderRadius: 5,
flex: 1,
alignItems: 'center',
margin: TABLE_ITEM_OFFSET,
width:
(SCREEN_WIDTH - TABLE_ITEM_MARGIN) / lineChartTables.length -
TABLE_ITEM_OFFSET,
height:
(SCREEN_WIDTH - TABLE_ITEM_MARGIN) / lineChartTables.length -
TABLE_ITEM_OFFSET,
maxWidth: 50,
maxHeight: 50
}
: {
justifyContent: 'center',
backgroundColor: 'white',
borderRadius: 5,
flex: 1,
alignItems: 'center',
margin: TABLE_ITEM_OFFSET,
width:
(SCREEN_WIDTH - TABLE_ITEM_MARGIN) / lineChartTables.length -
TABLE_ITEM_OFFSET,
height:
(SCREEN_WIDTH - TABLE_ITEM_MARGIN) / lineChartTables.length -
TABLE_ITEM_OFFSET,
maxWidth: 50,
maxHeight: 50
}
}
>
<Text style={activeChart == index ? chart.activeChartTxt : chart.chartTxt}>
{item}
</Text>
</TouchableHighlight>
);
return(
<View>
<Card2View item={{title:priceData.symbol, text:priceData.lastUpdatedPrice, money:`Rs. ${priceData.lastUpdatedPrice}`, procent:`${(priceData.percentageChange).toFixed(2)}`}} />
<View
style={{backgroundColor: 'white'}}>
<ChartPathProvider
data={{
points,
smoothingStrategy: 'bezier',
}}
>
<ChartPath height={SIZE / 2} stroke="black" strokeWidth="2" selectedOpacity="0.3" width={SIZE} />
<ChartDot
style={{
backgroundColor: 'black',
}}
size={15}
/>
{/* <ChartYLabel format={getX} style={{backgroundColor: 'white', color: 'black'}}/>
<ChartXLabel format={getY} style={{backgroundColor: 'white', color: 'black'}}/> */}
</ChartPathProvider>
<View style={{ flexDirection: 'row', justifyContent: 'space-around', flex: 1 }}>
{lineChartTables.map((data, index) => renderTable(data, index))}
</View>
</View>
</View>
)
}
export default LineChartView1;
const chart = StyleSheet.create({
chartTxt: {
fontSize: 14,
color: 'black'
},
activeChartTxt: {
fontSize: 14,
color: 'white',
fontWeight: 'bold'
}
});
You need to call the hook inside the ChartPathProvider. Example:
const ChildComponent = () => {
const values = useChartData();
return <Text>{values.greatestX}</Text>
}
const ParentComponent = ({ points }) => (
<ChartPathProvider
data={{
points,
smoothingStrategy: 'bezier',
}}
>
{/* other chart components */}
<ChildComponent />
</ChartPathProvider>
)
In your example, you are calling the hook before you define the provider (i.e. before the return statement).
in addition to the answer provided above by #Paul Kuhle
you can use the following to be able to access chartData
import React, {useContext} from 'react';
import ChartContext from '#rainbow-me/animated-charts/src/helpers/ChartContext';
const {positionX, positionY, dotScale, providedData, greatestY, layoutSize} = useContext(ChartContext);
and you may want to use useEffect to detect changes in the chart, let me know if that helps
I'm having the problem when runnig react-native run-android. Unless building on iOS is sucessfull but that on Android would not work. I use Animation UI, so can I realize that?
latlng cannot be null - a position is required
My code is below:
render() {
const interpolations = this.props.places.map((marker, index) => {
const inputRange = [
(index - 1) * CARD_WIDTH,
index * CARD_WIDTH,
((index + 1) * CARD_WIDTH),
];
const scale = this.animation.interpolate({
inputRange,
outputRange: [1, 2.5, 1],
extrapolate: "clamp",
});
const opacity = this.animation.interpolate({
inputRange,
outputRange: [0.35, 1, 0.35],
extrapolate: "clamp",
});
return { scale, opacity };
});
return (
<View style={styles.container}>
<MapView
provider={PROVIDER_GOOGLE}
ref={map => this.map = map}
region={{
latitude: this.state.coords.latitude,
longitude: this.state.coords.longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
style={styles.container}
>
{this.props.places.map((marker, index) => {
const scaleStyle = {
transform: [
{
scale: interpolations[index].scale,
},
],
};
const opacityStyle = {
opacity: interpolations[index].opacity,
};
return (
<MapView.Marker key={index} coordinate={marker.coordinate}>
<Animated.View style={[styles.markerWrap, opacityStyle]}>
<Animated.View style={[styles.ring, scaleStyle]} />
<View style={styles.marker} />
</Animated.View>
</MapView.Marker>
);
})}
</MapView>
}
And I added android.permission.ACCESS_FINE_LOCATION on AndroidManifest also.
I am following the tutorial in the link: https://medium.com/appandflow/react-native-scrollview-animated-header-10a18cb9469e .
However I can't seem to understand how to add tabs to this such that the tab headers get fixed to top when scrolled up. Can anyone please suggest how to achieve this?
I was able to achieve it using ScrollableTab and Tab in native-base library.
Code looks like this
<Tabs
prerenderingSiblingsNumber={3}
onChangeTab={({ i }) => {
this.setState({ height: this.heights[i], activeTab: i })
}}
renderTabBar={(props) => (
<Animated.View style={{ transform: [{ translateY: tabY }], zIndex: 1, width: "100%", backgroundColor: "white", paddingTop: HEADER_MAX_HEIGHT }}>
<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: tabBg
}}>
<TabHeading scrollable
style={{
backgroundColor: "transparent",
width: Dimensions.get('window').width / 2
}}
active={active}>
<Animated.Text style={{
fontWeight: active ? "bold" : "normal",
color: 'black',
padding: 10,
fontSize: active ? 20 : 18
}}>
{name}
</Animated.Text>
</TabHeading>
</Animated.View>
</TouchableOpacity>
)}
underlineStyle={{ backgroundColor: 'black' }}
/>
</Animated.View>
)}>
<Tab heading="Tab 1">
{this.tabContent(30, 0)}
</Tab>
<Tab heading="Tab 2">
{this.tabContent(15, 1)}
</Tab>
</Tabs>
use this:
expo install react-native-gesture-handler react-native-reanimated
Then basic usage look like this to replace in <Animated.View ......<Text>....</Text>..... ></Animated.View>:
<TabView
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={SceneMap({
first: FirstRoute,
second: SecondRoute,
})}
/>
I hope it'll help you..!
In case anybody else falls on this thread, I developed a new package, react-native-animated-screen, that does exactly what you need
Check it out
https://www.npmjs.com/package/react-native-animated-screen
import React from 'react';
import { Image, Text, View } from 'react-native';
import AnimatedScreen from 'react-native-animated-screen';
const Component = () => {
return (
<AnimatedScreen.Wrapper>
<AnimatedScreen.Header>
<View>
<Text>Title</Text>
<AnimatedScreen.CollapsibleElement>
<Text>Subtitle</Text>
</AnimatedScreen.CollapsibleElement>
<AnimatedScreen.Element>
<YourTabsComponen />
</AnimatedScreen.Element>
</View>
</AnimatedScreen.Header>
<AnimatedScreen.ScrollView>
<View style={{ height: '300%' }}>
<View>
<Text>Body</Text>
</View>
</View>
</AnimatedScreen.ScrollView>
</AnimatedScreen.Wrapper>
);
};
In the example above only the title and YourTabBComponentare going to remain after scrolling, the subtitle (as all the elements wrapped in a CollapsibleElement) will disappear.
Ideally you could also have the tabs to appear only when entirely scrolled using the interpolate prop or the animatedStyle of the AnimatedScreen.Element
// using interpolate the animation will flow from 0 to scrollY === headerMaxHeight
<AnimatedScreen.Element interpolate={{ opacity: [0, 1], height: [0, 60] }}>
<TabComponent />
</AnimatedScreen.Element>
// alternatevely you can have more control with an animatedStyle
<AnimatedScreen.Element animatedStyle={scrollY => ({
opacity: scrollY.interpolate({
inputRange: [100, 200],
outputRange: [0, 1]
extrapolate: 'clamp'
})
})}>
<TabComponent />
</AnimatedScreen.Element>
In case anybody else falls on this thread, I developed a new package, react-native-animated-screen, that does exactly what you need
Check it out
https://www.npmjs.com/package/react-native-animated-screen
import React from 'react';
import { Image, Text, View } from 'react-native';
import AnimatedScreen from 'react-native-animated-screen';
const Component = () => {
return (
<AnimatedScreen.Wrapper>
<AnimatedScreen.Header>
<View>
<Text>Title</Text>
<AnimatedScreen.CollapsibleElement>
<Text>Subtitle</Text>
</AnimatedScreen.CollapsibleElement>
<AnimatedScreen.Element>
<YourTabsComponen />
</AnimatedScreen.Element>
</View>
</AnimatedScreen.Header>
<AnimatedScreen.ScrollView>
<View style={{ height: '300%' }}>
<View>
<Text>Body</Text>
</View>
</View>
</AnimatedScreen.ScrollView>
</AnimatedScreen.Wrapper>
);
};
In the example above only the title and YourTabBComponentare going to remain after scrolling, the subtitle (as all the elements wrapped in a CollapsibleElement) will disappear.
Ideally you could also have the tabs to appear only when entirely scrolled using the interpolate prop or the animatedStyle of the AnimatedScreen.Element
// using interpolate the animation will flow from 0 to scrollY === headerMaxHeight
<AnimatedScreen.Element interpolate={{ opacity: [0, 1], height: [0, 60] }}>
<TabComponent />
</AnimatedScreen.Element>
// alternatevely you can have more control with an animatedStyle
<AnimatedScreen.Element animatedStyle={scrollY => ({
opacity: scrollY.interpolate({
inputRange: [100, 200],
outputRange: [0, 1]
extrapolate: 'clamp'
})
})}>
<TabComponent />
</AnimatedScreen.Element>