I was working with react native reanimated 1.13.1, but I decided to upgrade because of the new content and new features. I have been trying to fix an animation that I had on my Custom Drawer the animation consists on scaling the screen differently when the user accesses the drawer. It was working perfectly on 1.13.1 but on 2.3.0 I can't seem to get the scaling right with props.progress.
This is the code for the drawer.
import React, { useEffect } from 'react';
import { Container } from './styles';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { Register } from '../../screens/Register';
import { CustomDrawerContent } from '../CustomDrawerContent';
import { Dashboard } from '../../screens/Dashboard';
import { Summary } from '../../screens/Summary';
//REDUX
import { connect } from 'react-redux';
import { setSelectedTab } from '../../stores/tab/tabActions';
import { Settings } from '../../screens/Settings';
import { useSharedValue, interpolate, useAnimatedStyle, Extrapolate, withTiming } from 'react-native-reanimated';
const Drawer = createDrawerNavigator();
function CustomDrawer({ selectedTab, setSelectedTab }: any){
function setDefaultOnlyOnce() {
setSelectedTab('Dashboard');
}
let progress = useSharedValue(0);
const scale: any = useAnimatedStyle(() => {
return {
transform: [{
scale: interpolate(
progress.value,
[0, 1],
[1, 0.8],
Extrapolate.CLAMP
)
}]
};
})
const borderRadius: any = useAnimatedStyle(() => {
return {
borderRadius: interpolate(
progress.value,
[0, 1],
[0, 26],
Extrapolate.CLAMP
)
};
})
const animatedStyle = [[ borderRadius, scale ]];
useEffect(() => {
setDefaultOnlyOnce();
}, [])
return (
<Container>
<Drawer.Navigator
drawerType='slide'
overlayColor='transparent'
drawerStyle={{
flex: 1,
width: '70%',
paddingRight: 20,
backgroundColor: 'transparent'
}}
initialRouteName="Dashboard"
drawerContent={props => {
setTimeout(() => {
progress.value = 1;
/*
PREVIOUS VERSION:
setProgress(props.progress as any);
*/
}, 0);
return <CustomDrawerContent
navigation={props.navigation}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
}}
>
<Drawer.Screen name="Dashboard">
{props => <Dashboard drawerAnimationStyle={animatedStyle} />}
</Drawer.Screen>
<Drawer.Screen name="Register">
{props => <Register drawerAnimationStyle={animatedStyle} setSelectedTab={setSelectedTab}/>}
</Drawer.Screen>
<Drawer.Screen name="Summary">
{props => <Summary drawerAnimationStyle={animatedStyle}/>}
</Drawer.Screen>
<Drawer.Screen name="Settings">
{props => <Settings drawerAnimationStyle={animatedStyle}/>}
</Drawer.Screen>
</Drawer.Navigator>
</Container>
);
}
function mapStateToProps(state: any) {
return {
selectedTab: state.tabReducer.selectedTab
}
}
function mapDispatchToProps(dispatch: any) {
return {
setSelectedTab: (selectedTab: any) => { return dispatch(setSelectedTab(selectedTab)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomDrawer);
I think that the problem might be on setting progress.value where previously it setted the progress with props.progress but that doesn't seem to work for the new version.
When the progress.value is 0 the screen is completely filled (Drawer is closed) and when the progress.value is 1 the screen is resized and the drawer is opened. This, altered in the previous version with the props.progress being set on drawerContent
Related
I am trying to implement drawing navigation like in the image above using react native reanimated 2.
import React, { useState } from 'react'
import { Text } from 'react-native'
import { createDrawerNavigator } from '#react-navigation/drawer'
import Animated, { useSharedValue, interpolate, useAnimatedStyle } from 'react-native-reanimated'
import Screen1 from '../screens/screen1'
import Screen2 from '../screens/screen2'
import Screen3 from '../screens/screen3'
const Drawer = createDrawerNavigator()
const DrawerNavigator = () => {
const [translateX, setTranslateX] = useState(new Animated.Value(0))
//const translateX = useSharedValue(0)
const scale = Animated.interpolateNode(translateX, {
inputRange: [0, 1],
outputRange: [1, 0.8],
});
// const animatedStyle = useAnimatedStyle(() => {
// const scale = interpolate(
// translateX.value,
// [0, 1],
// [1, 0.8]
// );
// return{ transform: [{ scale }] }
// })
const animatedStyle = { transform: [{ scale }] };
return (
<Drawer.Navigator drawerContent={ (props) => {
setTranslateX(props.progress)
// translateX.value = props.progress
return <Text>Hello madan</Text>
}} >
<Drawer.Screen name="screen1">
{() => <Screen1 style={ animatedStyle } />}
</Drawer.Screen>
<Drawer.Screen name="screen2" component={ Screen2 } />
<Drawer.Screen name="screen3" component={ Screen3 } />
</Drawer.Navigator>
)
}
I can achieved it by using code above. But, i am using Animated.Value. I want to use useSharedValue() and useAnimatedStyle hook from reanimated 2. I tried to implement that which can be seen in the code above which are commented. But, its not working. App crashes and give error in JSCRuntime file.
I want to implement an animated accordion list/ drawer / drop-down menu / collapsible card.
The animation should be performant and look like this:
After a lot of searching, I could find many libraries. But I wanted to implement it without any library. Also, some tutorials showed how to build one, but they were not performant.
Finally, this is how I implemented it. The complete snack code is here: https://snack.expo.dev/#vipulchandra04/a85348
I am storing isOpen (whether the menu is open or closed) in a state. Then changing that state on button press. I am using the LayoutAnimation API in React-Native to animate the opening and closing of the list. LayoutAnimation runs the animation natively, thus it is performant.
const Accordion = ({ title, children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(value => !value);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}
return (
<>
<TouchableOpacity onPress={toggleOpen} activeOpacity={0.6}>
{title}
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</>
);
};
const styles = StyleSheet.create({
hidden: {
height: 0,
},
list: {
overflow: 'hidden'
},
});
With this, it will fix the Vipul's demo's error: if click accordion so fast, children's opacity descending to 0. and add animation for icon
import {
Animated,
LayoutAnimation,
Platform,
StyleProp,
StyleSheet,
UIManager,
View,
ViewStyle,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons;
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const toggleAnimation = duration => {
return {
duration: duration,
update: {
property: LayoutAnimation.Properties.scaleXY,
type: LayoutAnimation.Types.easeInEaseOut,
},
delete: {
property: LayoutAnimation.Properties.opacity,
type: LayoutAnimation.Types.easeInEaseOut,
},
};
};
interface IAccordion {
title?: JSX.Element | JSX.Element[];
children?: JSX.Element | JSX.Element[];
style?: StyleProp<ViewStyle> | undefined;
}
const Accordion = ({title, children, style}: IAccordion) => {
const [isOpen, setIsOpen] = useState(false);
const animationController = useRef(new Animated.Value(0)).current;
const arrowTransform = animationController.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
});
const onToggle = () => {
setIsOpen(prevState => !prevState);
const duration = 300;
const config = {
duration: duration,
toValue: isOpen ? 0 : 1,
useNativeDriver: true,
};
Animated.timing(animationController, config).start();
LayoutAnimation.configureNext(toggleAnimation(duration));
};
return (
<View style={style ? style : styles.accordion}>
<TouchableOpacity onPress={onToggle} style={styles.heading}>
{title}
<Animated.View style={{transform: [{rotateZ: arrowTransform}]}}>
<Ionicons name={'chevron-forward-outline'} size={18} />
</Animated.View>
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</View>
);
};
export default Accordion;
I had difficulty using the native API, so I go to third parties. The only thing I couldn't do was make the accordion size automatic.
import { useEffect } from 'react';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
Easing,
} from 'react-native-reanimated';
import styled from 'styled-components';
const Accordion = ({ children, open, height }) => {
const heightAnimation = useSharedValue(0);
useEffect(() => {
if (open === true) heightAnimation.value = height;
if (open === false) heightAnimation.value = 0;
}, [open]);
const animatedStyle = useAnimatedStyle(() => {
return {
height: withTiming(heightAnimation.value, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
};
});
return (
<>
<Main style={animatedStyle}>{children}</Main>
</>
);
};
const Main = styled(Animated.View)`
width: 100%;
align-items: center;
justify-content: center;
overflow: hidden;
`;
export default Accordion;
Using:
<Accordion height={height} open={open}>
{children}
</Accordion>
As asked here for an example of what I managed to do with it, I tried to get as much out of it as possible.
You can see a deploy here: https://snack.expo.dev/#francisco.ossian/accordion
Libs used, react-native-reanimated
I am using react-navigation 5 in my mobile app and I am stuck in implementing a log-off feature.
The scenario is I have a stack navigator and a bottom tab navigator in my app. The app starts with the stack navigator (Login feature and Reset Password Feature) and on login goes to the dashboard Page which is from the bottom tab navigator. Now on the Dashboard page, I am implementing a logout feature which when clicked should take me to the login page (part of stack navigator), and no matter what I try it keeps giving me errors like these
The action 'RESET' with payload {"index":0,"routes":[{"name":"AuthNavigator"}]} was not handled by any navigator.
Here are my code snippets right from start
Component Called from App.js
import React, { useState, useEffect, useContext } from "react";
import { ActivityIndicator } from "react-native";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { Center } from "../../components/Center";
import { AuthContext } from "../authentication/AuthProvider";
import { NavigationContainer } from "#react-navigation/native";
import { AuthNavigator } from "./AuthNavigator";
import { MainTabNavigator } from "./MainTabNavigator";
export default function App() {
const { user, login } = useContext(AuthContext);
const [loading, setLoading] = useState(true);
useEffect(() => {
// check if the user is logged in or not
//AsyncStorage.removeItem("user") //- uncomment this and refresh emulator to start from login screen
AsyncStorage.getItem("user")
.then(userString => {
if (userString) {
login();
}
setLoading(false);
})
.catch(err => {
console.log(err);
});
}, []);
if (loading) {
return (
<Center>
<ActivityIndicator size="large" />
</Center>
);
}
return (
<NavigationContainer>
{user ? <MainTabNavigator /> : <AuthNavigator />}
</NavigationContainer>
);
}
AuthNavigator.js
import React from "react";
import { createStackNavigator } from '#react-navigation/stack';
import Login from '../authentication/Login';
import ResetPassword from '../authentication/ResetPassword';
import { MainTabNavigator } from "./MainTabNavigator";
const Stack = createStackNavigator();
export const AuthNavigator = () => {
return (
<Stack.Navigator initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="ResetPassword" options={{headerTitle: "Reset Password"}} component={ResetPassword} />
</Stack.Navigator>
);
}
MainTabNavigator.js
import * as React from 'react';
import { Text, View, Image, StyleSheet, Platform } from 'react-native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import DashboardView from '../dashboard/DashboardView';
import Search from '../searchLoan/Search';
import { colors } from '../../styles';
const iconHome = require('../../../assets/images/tabbar/home.png');
const iconGrids = require('../../../assets/images/tabbar/grids.png');
const searchIcon = require('../../../assets/images/pages/search_24px.png');
const Tab = createBottomTabNavigator();
const tabData = [
{
name: 'Dashboard',
component: DashboardView,
icon: iconHome,
},
{
name: 'Search',
component: Search,
icon: searchIcon,
},
];
export const MainTabNavigator = () => {
return (
<Tab.Navigator tabBarOptions={{ style: { height: Platform.OS === 'ios' ? 90 : 50 } }}>
{tabData.map((item, idx) => (
<Tab.Screen
key={`tab_item${idx + 1}`}
name={item.name}
component={item.component}
options={{
tabBarIcon: ({ focused }) => (
<View style={styles.tabBarItemContainer}>
<Image
resizeMode="contain"
source={item.icon}
style={[styles.tabBarIcon, focused && styles.tabBarIconFocused]}
/>
</View>
),
tabBarLabel: ({ focused }) => <Text style={{ fontSize: 12, color: focused ? colors.primary : colors.gray }}>{item.name}</Text>,
title: item.name,
}}
/>
))}
</Tab.Navigator>
);
};
const styles = StyleSheet.create({
tabBarItemContainer: {
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 2,
borderBottomColor: colors.white,
paddingHorizontal: 10,
bottom: Platform.OS === 'ios' ? -5 : 0,
},
tabBarIcon: {
width: 23,
height: 23,
},
tabBarIconFocused: {
tintColor: colors.primary,
},
});
DashboardView.js
import React , {useContext }from 'react';
import { StyleSheet, View, TouchableOpacity, Text } from 'react-native';
import {Header} from 'react-native-elements';
import AntIcon from "react-native-vector-icons/AntDesign";
import { colors, fonts } from '../../styles';
import AmountDetails from './AmountDetails';
import DashboardFields from './DashboardFields';
import { AuthContext } from "../authentication/AuthProvider";
import { CommonActions } from "#react-navigation/native";
export default function DashboardView(props) {
const appLogOut = () => {
const { logout } = useContext(AuthContext);
console.log('props', props)
if(logout){
// console.log("Navigation Object", navigation)
props.navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: "AuthNavigator" }],
}));
}
}
return (
<View style={styles.container}>
<Header containerStyle = {{backgroundColor: colors.primary}}
centerComponent={{ text: 'Dashboard', style: { color: colors.white, backgroundColor: colors.primary } }}
rightComponent = <TouchableOpacity onPress={appLogOut()}><AntIcon name="logout" color="white" size={25}/></TouchableOpacity>
/>
<View style={styles.container}>
<View>
<AmountDetails />
</View>
<View style={styles.dashboardFields}>
<DashboardFields />
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.gray,
},
dashboardFields: {
marginTop: 20,
},
});
You should try calling the login screen directly, not the whole stack.
CommonActions.reset({
index: 0,
routes: [{ name: "Login" }],
}));
As the other answer said, you have incorrect route name (AuthNavigator).
However, you're conditionally defining screens based on if the user is logged in. You don't need to do anything extra when logging out. Conditionally defining screens means React Navigation can automatically handle which screen to show when the conditional changes.
So you need to remove the code which does reset.
From the docs:
It's important to note that when using such a setup, you don't need to manually navigate to the Home screen by calling navigation.navigate('Home') or any other method. React Navigation will automatically navigate to the correct screen when isSigned in changes - Home screen when isSignedIn becomes true, and to SignIn screen when isSignedIn becomes false. You'll get an error if you attempt to navigate manually.
More details: https://reactnavigation.org/docs/auth-flow/
I'm trying to create a button that resizes (gets a little bit smaller when it' pressed). I use TouchableWithoutFeedback from react-native-gesture-handlerand I use react-native-reanimated.
This is my code so far:
import React, { useState } from 'react';
import { View } from 'react-native';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
const { interpolate, sub } = Animated;
const TouchableResize = (props) => {
const { onPress, children } = props;
const [scale, setScale] = useState(0);
const scaling = interpolate(scale, {
inputRange: [0, 1],
outputRange: [1, 0.90],
extrapolate: Extrapolate.CLAMP
});
return (
<TouchableWithoutFeedback onPressIn={() => setScale(1)} onPressOut={() => setScale(0)}>
<Animated.View style={{ transform: [{ scaleX: scaling }, { scaleY: scaling }] }}>
{children}
</Animated.View>
</TouchableWithoutFeedback>
);
};
export { TouchableResize };
This code works partly. The button resizes to 0.90 when it's pressed, but the animation is not smooth. It snaps directly to 0.90, and when it's released, the button directly snaps back.
How can I update my code so the animation runs smoothly? Please note I'm a complete beginner in react-native-reanimated.
You have to use timing function to change your Animated.Value over time. Here example in docs. Also, I created expo snack example. Here updated component code
import React, { useState, useMemo } from 'react';
import { View } from 'react-native';
import Animated, { Easing, Extrapolate } from 'react-native-reanimated';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
const {
Clock,
Value,
set,
cond,
startClock,
clockRunning,
timing,
debug,
stopClock,
block,
interpolate,
useCode,
} = Animated;
function runTiming(clock, from, to) {
const state = {
finished: new Value(0),
position: new Value(from),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 100,
toValue: new Value(to),
easing: Easing.inOut(Easing.ease),
};
return block([
cond(
clockRunning(clock),
[],
startClock(clock),
),
// we run the step here that is going to update position
timing(clock, state, config),
// if the animation is over we stop the clock
cond(state.finished, debug('stop clock', stopClock(clock))),
// we made the block return the updated position
state.position,
]);
}
const TouchableResize = (props) => {
const { onPress, children } = props;
const [pressed, setPressed] = useState(false);
const {clock, scale} = useMemo(() => ({
clock: new Clock(),
scale: new Value(1),
}), [])
useCode(
() => block([
pressed ? set(scale, runTiming(clock, 0, 1)) : set(scale, runTiming(clock, 1, 0))
]), [pressed]
);
const scaling = interpolate(scale, {
inputRange: [0, 1],
outputRange: [1, 0.90],
extrapolate: Extrapolate.CLAMP
});
return (
<TouchableWithoutFeedback onPressIn={() => setPressed(true)} onPressOut={() => setPressed(false)}>
<Animated.View style={{ transform: [{ scaleX: scaling }, { scaleY: scaling }] }}>
{children}
</Animated.View>
</TouchableWithoutFeedback>
);
};
export { TouchableResize };
in my react-native project, I'm trying to use a callback when the State of LongPressGestureHandler becomes END | FAILED | CANCELLED. The function for this callback uses a global variable reactions that I receive as props. Even after this prop gets updated. The function somehow is using the old reactions. I'll be really thankful if you could explain what I'm doing wrong.
`
import React, { useState, useEffect } from 'react';
import { useNavigation } from '#react-navigation/native';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import { Vibration, View, Text } from 'react-native';
import Animated from 'react-native-reanimated';
import { useGestureHandler } from 'react-native-redash';
import useReaction from '../../../../../../services/dashboard/feed/components/reaction/api';
import assets from '../../../../../../assets';
import styles from './styles';
const {
Value, useCode, block, eq, call, onChange, cond, and, or, set
} = Animated;
export default ({
reactions,
emoji,
componentRefetch,
loggedInUsersId,
itemId,
counts,
}) => {
const { navigate } = useNavigation();
const [giveReaction, giveUnReaction] = useReaction(componentRefetch);
const reacted = reactions.find(
(reaction) => (reaction.user ? reaction.user.id === loggedInUsersId : false)
&& reaction.emoji === emoji
);
const [popAnim] = useState(new Value(1));
const onEmojiPress = async () => {
console.log('Emoji Pressed');
console.log(reactions.length);
if (reacted) {
await giveUnReaction(itemId, emoji, reacted.id);
} else {
await giveReaction(itemId, emoji);
}
};
const onEmojiLongPress = () => {
console.log('Emoji long Pressed');
Vibration.vibrate(1);
navigate(assets.strings.dashboard.feeds.reactantsPopup.NAME, {
reactions,
emoji,
});
};
const longPressState = new Value(State.UNDETERMINED);
const longPressGestureHandler = useGestureHandler({ state: longPressState });
const shouldScale = new Value(-1);
useCode(
() => block([
cond(
eq(longPressState, State.BEGAN),
set(shouldScale, 1)
),
cond(
eq(longPressState, State.FAILED),
call([], onEmojiPress)
),
cond(
or(
eq(longPressState, State.CANCELLED),
eq(longPressState, State.END),
),
call([], onEmojiLongPress)
),
]),
[]
);
return (
<LongPressGestureHandler {...longPressGestureHandler} minDurationMs={100}>
<Animated.View
style={{
...styles.icon,
borderColor: reacted
? assets.colors.appDefaults.primaryColor
: '#eeeeee',
backgroundColor: reacted
? assets.colors.appDefaults.primaryColorLight
: '#eeeeee',
}}
>
<Animated.View
style={{
transform: [
{ scale: popAnim },
{
translateY: popAnim.interpolate({
inputRange: [1, 1.3],
outputRange: [0, -2],
}),
},
],
}}
>
<Text
style={{
color: reacted ? assets.colors.appDefaults.primaryColor : 'black',
}}
>
{`${emoji ?? '👍'} `}
</Text>
</Animated.View>
<Text
style={{
color: reacted ? assets.colors.appDefaults.primaryColor : 'black',
}}
>
{`${counts[emoji]}`}
</Text>
</Animated.View>
</LongPressGestureHandler>
);
};
`
I found out that(by experimenting) useCode sends a set of instructions to native side, in order to change the set of instructions to latest prop update, we need to fire the useCode on prop update.
useCode(, [dependencyThatGetsUpdated])