How to use FlatList with sticky headers in animated.ScrollView - android

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

ReactNative animation is not working after the api call

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.

Pan Responder with transform not working in Android react native?

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

How to use the helper function provided by a library?

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

latlng cannot be null - a position is required(React Native)

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.

Tabs with scrolling animated header - React native

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>

Categories

Resources