Related
We are currently using react-navigation (5.6) in our react native app.
We use a BottomTabNavigator as the root navigation.
const BottomTab = createBottomTabNavigator()
The individual screens under this BottomTab.Navigator are navigators themselves. e.g.
<BottomTab.Screen
name={AppRoute.MORE}
component={MoreTabNavigator}
options={{
title: 'More',
tabBarIcon: ({ color, size }) => {
return <Icon style={{ width: size, height: size }} fill={color} name="more-horizontal-outline" />
}
}}
/>
The MoreTabNavigator is as follows:
const MoreTabNavigator = (): React.ReactElement => {
const { Navigator, Screen } = createStackNavigator<RootStackParamList>();
return (
<Navigator headerMode="none"
screenOptions={{
headerShown: false,
cardStyle: { backgroundColor: 'transparent' },
cardOverlayEnabled: true,
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress.interpolate({
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
}),
},
overlayStyle: {
opacity: progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.5],
extrapolate: 'clamp',
}),
},
}),
}}
mode="modal">
<Screen
name={AppRoute.MORE}
component={MoreScreen}
/>
<Screen
name={AppRoute.CONTACT_US}
component={ContactUsScreen}
/>
</Navigator>
)
}
What I am trying to achieve is that when user clicks More tab, it should show a transparent overlay with the previous screen in the background. I want to create something like this
I am not sure how this is to be done. I have tried different solutions given on SO but haven't been able to achieve the desired effect. I always get a solid color grey background.
I tried with version 6 as well using presentationalModal however no luck. I am looking for react-navigation v5 or v6 solution
Any help or pointers would be really helpful.
Thanks in advance
i need some help about my custom drawer .
it used to work perfectly with slide animation but after updating to drawer v6.
the package react-native-reanimated has been updated too.
he stops work. can you help me guys. thanks
const CustomDrawer = ({navigation}) => {
const [progress, setProgress] = React.useState(new Animated.Value(0));
const scale = Animated.interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [1, 0.8],
});
const borderRadius = Animated.interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [0, 26],
});
const animatedStyle = {borderRadius, transform: [{scale}]};
return (
<View style={{flex: 1, backgroundColor: COLORS.primary}}>
<Drawer.Navigator
screenOptions={{
headerShown: false,
sceneContainerStyle: {backgroundColor: 'transparent'},
drawerType: 'slide',
drawerStyle: {
flex: 1,
width: '65%',
paddingRight: 20,
backgroundColor: 'transparent',
},
}}
drawerContent={props => {
// setTimeout(() => {
setProgress(props.progress);
// }, 0);
return <CustomContentDrawer navigation={props.navigation} />;
}}>
<Drawer.Screen name="MainLayout">
{props => (
<MainLayout
{...props}
drawerAnimationStyle={animatedStyle}
navigation={navigation}
/>
)}
</Drawer.Screen>
</Drawer.Navigator>
</View>
);
};
const MainLayout = ({drawerAnimationStyle, navigation}) => {
return (
<Animated.View
style={{flex: 1, backgroundColor: 'white', ...drawerAnimationStyle}}>
<Text>MainLayout</Text>
</Animated.View>
);
};
With the latest update progress prop of drawerContent seems to return undefined. So the animation is not working.
Instead of using useState , we can use useDrawerProgress() hook in the Main component instead of Drawer component. All the animation logic needs to be implemented in Main Component.
//Main Component
const MainLayout = (props) => {
const progress = useDrawerProgress();
const scale = Animated.interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [1, 0.8],
});
const borderRadius = Animated.interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [0, 26],
});
const animatedStyle = {
borderRadius,
transform: [{ scale }],
};
return (
<Animated.View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "white",
...animatedStyle,
}}
>
<Text>MainLayout</Text>
</Animated.View>
);
};
export default MainLayout;
PS: remove any animation logic and props passed in drawer component. We don't need them anymore.
useDrawerProgress - working
try this code:
const drawerProgress = useDrawerProgress();
const animatedStyle = useAnimatedStyle(() => {
const scale = interpolate(drawerProgress.value, [0, 1], [1, 0.8], {
extrapolateRight: Extrapolate.CLAMP,
});
const borderRadius = interpolate(drawerProgress.value, [0, 1], [0, 10], {
extrapolateRight: Extrapolate.CLAMP,
});
return {
transform: [{scale}],
borderRadius,
};
});
But write this code inside screens not in the DrawerContent, for me its working!!
I need to create a custom animation effect, which is pretty similar with the default one on iOS, but with a small difference: When I change the screen, I need the next screen to push the current screen to the left. The default animation is placing the next screen over the current one and it also moves the current screen to the left. But I need the next screen to literally push the current screen because I have a wave which starts from the first screen and continues to the second screen and I would like to achieve a continuation effect when changing the screens.
This is what I have for now, but it is just hiding the current screen on screen change:
<Stack.Navigator
initialRouteName="Language"
screenOptions={{
cardOverlayEnabled: true,
gestureEnabled: true,
...MyTransition,
}}>
...
</Stack.Navigator>
const MyTransition = {
gestureDirection: 'horizontal',
transitionSpec: {
open: TransitionSpecs.TransitionIOSSpec,
close: TransitionSpecs.TransitionIOSSpec,
},
cardStyleInterpolator: ({ current, next, layouts }) => {
return {
cardStyle: {
transform: [
{
translateX: current.progress.interpolate({
inputRange: [0, 1],
outputRange: [layouts.screen.width, 0],
}),
},
],
},
};
},
};
Thanks for your help!
I just coming across this issue and i think i find the solution on react navigation official site.
const cardStyleInterpolator = ({
current,
next,
inverted,
layouts: { screen }
}) => {
const progress = Animated.add(
current.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: "clamp"
}),
next
? next.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: "clamp"
})
: 0
);
return {
cardStyle: {
transform: [
{
translateX: Animated.multiply(
progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
screen.width, // Focused, but offscreen in the beginning
0, // Fully focused
//I changed this line only
//screen.width * -0.3 // Fully unfocused
-screen.width
],
extrapolate: "clamp"
}),
inverted
)
}
]
}
};
};
https://reactnavigation.org/docs/stack-navigator
Usage:
<Stack.Navigator
screenOptions={{
headerShown: false, //Optional
cardStyle: { backgroundColor: "transparent" }, //Optional
cardStyleInterpolator
}}
>
<Stack.Screen name="ScreenA" component={ScreenA} />
<Stack.Screen name="ScreenB" component={ScreenB} />
</Stack.Navigator>
I've just copied the MapView with the Animated API from react-native-map. It works but the performance is too slow in android devices (Samsung). Has anyone experience it? How can it be solved?
Have a look at the video here
P.S I don't need the component on top of the map to move upward so I have deleted the transform Y. Keeping it or not has no effect in performance. The app hangs after sometime.
code:
import React from 'react';
import {
StyleSheet,
View,
Dimensions,
Animated,
} from 'react-native';
import {
ProviderPropType,
Animated as AnimatedMap,
AnimatedRegion,
Marker,
} from 'react-native-maps';
import PanController from './PanController';
import PriceMarker from './AnimatedPriceMarker';
const screen = Dimensions.get('window');
const ASPECT_RATIO = screen.width / screen.height;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const ITEM_SPACING = 10;
const ITEM_PREVIEW = 10;
const ITEM_WIDTH = screen.width - (2 * ITEM_SPACING) - (2 * ITEM_PREVIEW);
const SNAP_WIDTH = ITEM_WIDTH + ITEM_SPACING;
const ITEM_PREVIEW_HEIGHT = 150;
const SCALE_END = screen.width / ITEM_WIDTH;
const BREAKPOINT1 = 246;
const BREAKPOINT2 = 350;
const ONE = new Animated.Value(1);
function getMarkerState(panX, panY, scrollY, i) {
const xLeft = (-SNAP_WIDTH * i) + (SNAP_WIDTH / 2);
const xRight = (-SNAP_WIDTH * i) - (SNAP_WIDTH / 2);
const xPos = -SNAP_WIDTH * i;
const isIndex = panX.interpolate({
inputRange: [xRight - 1, xRight, xLeft, xLeft + 1],
outputRange: [0, 1, 1, 0],
extrapolate: 'clamp',
});
const isNotIndex = panX.interpolate({
inputRange: [xRight - 1, xRight, xLeft, xLeft + 1],
outputRange: [1, 0, 0, 1],
extrapolate: 'clamp',
});
const center = panX.interpolate({
inputRange: [xPos - 10, xPos, xPos + 10],
outputRange: [0, 1, 0],
extrapolate: 'clamp',
});
const selected = panX.interpolate({
inputRange: [xRight, xPos, xLeft],
outputRange: [0, 1, 0],
extrapolate: 'clamp',
});
const translateY = Animated.multiply(isIndex, panY);
const translateX = panX;
const anim = Animated.multiply(isIndex, scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [0, 1],
extrapolate: 'clamp',
}));
const scale = Animated.add(ONE, Animated.multiply(isIndex, scrollY.interpolate({
inputRange: [BREAKPOINT1, BREAKPOINT2],
outputRange: [0, SCALE_END - 1],
extrapolate: 'clamp',
})));
// [0 => 1]
let opacity = scrollY.interpolate({
inputRange: [BREAKPOINT1, BREAKPOINT2],
outputRange: [0, 1],
extrapolate: 'clamp',
});
// if i === index: [0 => 0]
// if i !== index: [0 => 1]
opacity = Animated.multiply(isNotIndex, opacity);
// if i === index: [1 => 1]
// if i !== index: [1 => 0]
opacity = opacity.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
});
let markerOpacity = scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [0, 1],
extrapolate: 'clamp',
});
markerOpacity = Animated.multiply(isNotIndex, markerOpacity).interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
});
const markerScale = selected.interpolate({
inputRange: [0, 1],
outputRange: [1, 1.2],
});
return {
translateY,
translateX,
scale,
opacity,
anim,
center,
selected,
markerOpacity,
markerScale,
};
}
class AnimatedViews extends React.Component {
constructor(props) {
super(props);
const panX = new Animated.Value(0);
const panY = new Animated.Value(0);
const scrollY = panY.interpolate({
inputRange: [-1, 1],
outputRange: [1, -1],
});
const scrollX = panX.interpolate({
inputRange: [-1, 1],
outputRange: [1, -1],
});
const scale = scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [1, 1.6],
extrapolate: 'clamp',
});
const translateY = scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [0, -100],
extrapolate: 'clamp',
});
const markers = [
{
id: 0,
amount: 99,
coordinate: {
latitude: 27.6741672,
longitude: 85.3094676,
},
},
{
id: 1,
amount: 199,
coordinate: {
latitude: 27.685064,
longitude: 85.298746,
},
},
{
id: 2,
amount: 285,
coordinate: {
latitude: 27.6741672,
longitude: 85.3094676,
},
},
];
const animations = markers.map((m, i) =>
getMarkerState(panX, panY, scrollY, i));
this.state = {
panX,
panY,
animations,
index: 0,
canMoveHorizontal: true,
scrollY,
scrollX,
scale,
translateY,
markers,
region: new AnimatedRegion({
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}),
};
}
componentDidMount() {
const { region, panX, panY, scrollX, markers } = this.state;
panX.addListener(this.onPanXChange);
panY.addListener(this.onPanYChange);
region.stopAnimation();
region.timing({
latitude: scrollX.interpolate({
inputRange: markers.map((m, i) => i * SNAP_WIDTH),
outputRange: markers.map(m => m.coordinate.latitude),
}),
longitude: scrollX.interpolate({
inputRange: markers.map((m, i) => i * SNAP_WIDTH),
outputRange: markers.map(m => m.coordinate.longitude),
}),
duration: 0,
}).start();
}
onStartShouldSetPanResponder = (e) => {
// we only want to move the view if they are starting the gesture on top
// of the view, so this calculates that and returns true if so. If we return
// false, the gesture should get passed to the map view appropriately.
const { panY } = this.state;
const { pageY } = e.nativeEvent;
const topOfMainWindow = ITEM_PREVIEW_HEIGHT + panY.__getValue();
const topOfTap = screen.height - pageY;
return topOfTap < topOfMainWindow;
}
onMoveShouldSetPanResponder = (e) => {
const { panY } = this.state;
const { pageY } = e.nativeEvent;
const topOfMainWindow = ITEM_PREVIEW_HEIGHT + panY.__getValue();
const topOfTap = screen.height - pageY;
return topOfTap < topOfMainWindow;
}
onPanXChange = ({ value }) => {
const { index } = this.state;
const newIndex = Math.floor(((-1 * value) + (SNAP_WIDTH / 2)) / SNAP_WIDTH);
if (index !== newIndex) {
this.setState({ index: newIndex });
}
}
onPanYChange = ({ value }) => {
const { canMoveHorizontal, region, scrollY, scrollX, markers, index } = this.state;
const shouldBeMovable = Math.abs(value) < 2;
if (shouldBeMovable !== canMoveHorizontal) {
this.setState({ canMoveHorizontal: shouldBeMovable });
if (!shouldBeMovable) {
const { coordinate } = markers[index];
region.stopAnimation();
region.timing({
latitude: scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [
coordinate.latitude,
coordinate.latitude - (LATITUDE_DELTA * 0.5 * 0.375),
],
extrapolate: 'clamp',
}),
latitudeDelta: scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [LATITUDE_DELTA, LATITUDE_DELTA * 0.5],
extrapolate: 'clamp',
}),
longitudeDelta: scrollY.interpolate({
inputRange: [0, BREAKPOINT1],
outputRange: [LONGITUDE_DELTA, LONGITUDE_DELTA * 0.5],
extrapolate: 'clamp',
}),
duration: 0,
}).start();
} else {
region.stopAnimation();
region.timing({
latitude: scrollX.interpolate({
inputRange: markers.map((m, i) => i * SNAP_WIDTH),
outputRange: markers.map(m => m.coordinate.latitude),
}),
longitude: scrollX.interpolate({
inputRange: markers.map((m, i) => i * SNAP_WIDTH),
outputRange: markers.map(m => m.coordinate.longitude),
}),
duration: 0,
}).start();
}
}
}
onRegionChange(/* region */) {
// this.state.region.setValue(region);
}
render() {
const {
panX,
panY,
animations,
canMoveHorizontal,
markers,
region,
} = this.state;
return (
<View style={styles.container}>
<PanController
style={styles.container}
vertical
horizontal={canMoveHorizontal}
xMode="snap"
snapSpacingX={SNAP_WIDTH}
yBounds={[-1 * screen.height, 0]}
xBounds={[-screen.width * (markers.length - 1), 0]}
panY={panY}
panX={panX}
onStartShouldSetPanResponder={this.onStartShouldSetPanResponder}
onMoveShouldSetPanResponder={this.onMoveShouldSetPanResponder}
>
<AnimatedMap
provider={this.props.provider}
style={styles.map}
region={region}
onRegionChange={this.onRegionChange}
>
{markers.map((marker, i) => {
const {
selected,
markerOpacity,
markerScale,
} = animations[i];
return (
<Marker
key={marker.id}
coordinate={marker.coordinate}
>
<PriceMarker
style={{
opacity: markerOpacity,
transform: [
{ scale: markerScale },
],
}}
amount={marker.amount}
selected={selected}
/>
</Marker>
);
})}
</AnimatedMap>
<View style={styles.itemContainer}>
{markers.map((marker, i) => {
const {
translateX,
scale,
opacity,
} = animations[i];
return (
<Animated.View
key={marker.id}
style={[styles.item, {
opacity,
transform: [
{ translateX },
{ scale },
],
}]}
/>
);
})}
</View>
</PanController>
</View>
);
}
}
AnimatedViews.propTypes = {
provider: ProviderPropType,
};
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
},
itemContainer: {
backgroundColor: 'transparent',
flexDirection: 'row',
paddingHorizontal: (ITEM_SPACING / 2) + ITEM_PREVIEW,
position: 'absolute',
// top: screen.height - ITEM_PREVIEW_HEIGHT - 64,
paddingTop: screen.height - ITEM_PREVIEW_HEIGHT - 64,
// paddingTop: !ANDROID ? 0 : screen.height - ITEM_PREVIEW_HEIGHT - 64,
},
map: {
backgroundColor: 'transparent',
...StyleSheet.absoluteFillObject,
},
item: {
width: ITEM_WIDTH,
height: screen.height + (2 * ITEM_PREVIEW_HEIGHT),
backgroundColor: 'red',
marginHorizontal: ITEM_SPACING / 2,
overflow: 'hidden',
borderRadius: 3,
borderColor: '#000',
},
});
export default AnimatedViews;
I found the issue is related to the Marker component, if you use children or the image prop there will be 100% CPU usage and will drop the UI thread to zero when using google maps. Try and recreate the marker using the icon prop, that will fix the issue.
Change
<Marker
key={marker.id}
coordinate={marker.coordinate}
>
<PriceMarker
style={{
opacity: markerOpacity,
transform: [{ scale: markerScale }]}
}
amount={marker.amount}
selected={selected}
/>
</Marker>
to
<Marker
key={marker.id}
coordinate={marker.coordinate}
icon={PriceMarkerIcon}
/>
Like Mahdi Bashirpour wrote here, you can also disable props tracksViewChanges={false} to fix the issue with 100% CPU usage by the Marker component.
I am using react-navigation to navigate from one screen to another.
By the way I am using createStackNavigator.
I am using the code below to navigate between screens.
<Button onPress={()=>this.props.navigation.navigate('ScreenTwo')}>button-></Button>
It works fine, but I want to change the animation direction. Currently when I press the button the ScreenTwo just pops up, instead I want the screen to slide from right to left.
Is the a way I could change the direction of the animation when navigating?
Answered by satya164 in react-navigation/stack github repo, using gestureDirection: 'horizontal-inverted' in defaultNavigationOptions/navigationOptions
Screen: {
screen: Screen,
navigationOptions: {
...TransitionPresets.SlideFromRightIOS,
gestureDirection: 'horizontal-inverted',
},
},
related links below:
https://github.com/react-navigation/stack/issues/377#issuecomment-578504696
https://reactnavigation.org/docs/stack-navigator/#animation-related-options
You need to use Custom Screen Transitions in side your navigation configurations. Try following code, (make sure to import Easing, Animated from 'react-native')
const yourStack = createStackNavigator(
{
One: ScreenOne,
Two: DetailsTwo,
},
{
initialRouteName: 'One',
transitionConfig: () => ({
transitionSpec: {
duration: 300,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
},
screenInterpolator: sceneProps => {
const {layout, position, scene} = sceneProps;
const {index} = scene;
const width = layout.initWidth;
const translateX = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [width, 0, 0],
});
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
});
return {opacity, transform: [{translateX: translateX}]};
},
})
}
);
For me this worked well with "react-native": "0.62.2","react-navigation": "4.4.4", "react-navigation-stack": "2.10.4",:
import {createStackNavigator, CardStyleInterpolators} from '#react-navigation/stack';
const RootStack = createStackNavigator();
function Root(props) {
return (
<RootStack.Navigator headerMode="none" mode="modal">
<RootStack.Screen
name="NextScreen"
options={{
gestureDirection: 'horizontal',
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
}}
component={NextScreenComponent}
/>
</RootStack.Navigator>
)}
For version 4.x.x -
import {
createStackNavigator,
CardStyleInterpolators,
} from 'react-navigation-stack';
const CatalogStack = createStackNavigator(
{
Catalog: Catalog,
ProductDetails: ProductDetails,
EditProduct: EditProduct,
Categories: Categories,
SubCategories: SubCategories,
ChooseColors: ChooseColors,
ChooseSizes: ChooseSizes,
},
{
defaultNavigationOptions: {
headerShown: false,
gestureEnabled: false,
swipeEnabled: false,
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
},
},
);
For 5.x.x -
import {
createStackNavigator,
CardStyleInterpolators,
} from '#react-navigation/stack';
<HomeStack.Navigator
initialRouteName="Home"
headerMode="none"
screenOptions={{
gestureEnabled: false,
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
}}>
<HomeStack.Screen name="Home" component={Home} />
</HomeStack.Navigator>
This worked for me:
<AppStack.Navigator headerMode="none" initialRouteName="Home">
<AppStack.Screen
name="LeftMenu"
component={LeftMenu}
options={{ gestureDirection: "horizontal-inverted" }}
/>
</AppStack.Navigator>
// some import
import { Animated, Easing } from 'react-native'
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const forSlide = ({ current, next, inverted, layouts: { screen } }) => {
const progress = Animated.add(
current.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
}),
next
? next.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
})
: 0
);
return {
cardStyle: {
transform: [
{
translateX: Animated.multiply(
progress.interpolate({
inputRange: [0, 1, 2],
outputRange: [
screen.width, // Focused, but offscreen in the beginning
0, // Fully focused
screen.width * -0.3, // Fully unfocused
],
extrapolate: 'clamp',
}),
inverted
),
},
],
},
};
};
const config = {
duration: 300,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
};
const SettingsNavigation = () => (
<Stack.Navigator screenOptions={{ tabBarVisible: false }}>
<Stack.Screen name="Tags" component={TagsScreen} options={{ headerShown: false, transitionSpec: { open: config, close: config }, cardStyleInterpolator: forSlide}} />
</Stack.Navigator>
);
I found this solution on https://reactnavigation.org/docs/stack-navigator/#animations