I currently have a loading screen that renders an animation. After doing so I would like it to immediately, based on firebase.auth().onAuthStateChange, navigate the user to a specific page.
I have already implemented the animation and part of the logic. I just need the ability to navigate immediately after the first render/animation has completed.
class LoadingScreen extends Component {
state = {
opacity: new Animated.Value(0),
}
onLoad = () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1500,
delay: 1000,
useNativeDriver: true,
}).start();
}
render() {
return (
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
transform: [
{
scale: this.state.opacity.interpolate({
inputRange: [0, 1],
outputRange: [0.85, 1],
})
},
],
},
this.props.style,
]}
/>
);
}
}
export default class App extends Component{
render()
{
return (
<View style={styles.container}>
<LoadingScreen
style={styles.image}
source= {require('../assets/images/logo.png')}
/>
</View>
)
}
checkIfLoggedIn = () => {
firebase.auth().onAuthStateChanged((user)=>
{
if(user)
{
this.props.navigation.navigate('Login');
}
else
{
this.props.navigation.navigate('Signup');
}
})
}
}
To do something on the end of the animation, you should add a callback to the start() function, so:
Pass your checkIfLoggedIn function as a prop to LoadingScreen component
<LoadingScreen
style={styles.image}
source= {require('../assets/images/logo.png')}
onAnimationEnd={this.checkIfLoggedIn}
/>
Use the function passed as a prop for the animation callback
onLoad = () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1500,
delay: 1000,
useNativeDriver: true,
}).start(() => this.props.onAnimationEnd());
}
Related
I am trying to set display:none on a parent element once the children stagger animations have completed. My li elements fade out and the parent ul should then update to display:none
I can set a delay in the transition, but trying to tap into the when property. I have tried:
const variants = {
open: {
display: 'block',
transition: {
staggerChildren: 0.17,
delayChildren: 0.2,
}
},
closed: {
display: 'none',
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
display: {
when: "afterChildren" // delay: 1 - this will work
}
}
}
};
Clearly I am getting the syntax incorrect or cannot be used as I intend.
Sandbox Demo
import * as React from "react";
import { render } from "react-dom";
import {motion, useCycle} from 'framer-motion';
const ulVariants = {
open: {
display: 'block',
visibility: 'visible',
transition: {
staggerChildren: 0.17,
delayChildren: 0.2,
}
},
closed: {
display: 'none',
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
display: {
when: "afterChildren" // delay: 1 - will work
}
}
}
};
const liVariants = {
open: {
y: 0,
opacity: 1,
transition: {
y: {stiffness: 1000, velocity: -100}
}
},
closed: {
y: 50,
opacity: 0,
transition: {
y: {stiffness: 1000}
}
}
}
const Item = (props) => (
<motion.li
variants={liVariants}
>
{props.name}
</motion.li>
)
const App = () => {
const [isOpen, toggleOpen] = useCycle(false, true);
return (
<>
<button onClick={toggleOpen}>Toggle Animation</button>
<motion.ul
variants={ulVariants}
animate={isOpen ? 'open': 'closed'}
>
{Array.from(['vader', 'maul', 'ren']).map((item, index) => (
<Item key={item} {...{name: item}} />
))}
</motion.ul>
</>
);
};
render(<App />, document.getElementById("root"));
when should be a property of the transition object, not display.
This seems to work (unless I'm misunderstanding what you're trying to do):
closed: {
display: 'none',
transition: {
staggerChildren: 0.05,
staggerDirection: -1,
when: "afterChildren"
}
}
Code Sandbox
You can use
<motion.div
animate={{
transitionEnd: {
display: "none",
},
}}
/>
When animating to a non-animatable value like "block", this value will be set instantly. By setting this value within transitionEnd, this value will be set at the end of the animation.
source: https://www.framer.com/docs/component
I have following navigation stack
const AppNavigator = createStackNavigator({
AppSplashScreen: AppSplashScreen,
LanguageScreen: LanguageScreen,
WalkthroughScreen: WalkthroughScreen,
LoginScreen: LoginScreen,
ForgotPasswordScreen: ForgotPasswordScreen,
ResetPasswordScreen: ResetPasswordScreen,
RegistrationTypeScreen: RegistrationTypeScreen,
RegistrationFormScreen: RegistrationFormScreen,
OTPConfirmationScreen: OTPConfirmationScreen,
BottomTabNavigator: BottomTabNavigator
}, {
headerMode: 'none',
cardStyle: { backgroundColor: '#000000' },
});
const AppContainer = createAppContainer(AppNavigator);
export default App;
I am displaying splash screen video when the first app opens.
Here is what my AppSplashScreen looks like
import React, { Component } from 'react';
import { View } from 'react-native';
import SplashScreen from 'react-native-splash-screen';
import Video from 'react-native-video';
import { VIDEO_SPLASH_2 } from '../assets/videos/index';
export default class AppSplashScreen extends Component {
state = {
displayVideoPlayer: true,
firstLaunch: false
}
componentDidMount() {
SplashScreen.hide();
}
componentWillUnmount() {
this.setState({
displayVideoPlayer: false
});
}
isFirstLaunch() {
let firstLaunch = true;
if (true === storage.get('APP_ALREADY_LAUNCHED')) {
firstLaunch = false;
} else {
storage.set('APP_ALREADY_LAUNCHED', true);
firstLaunch = true;
}
return firstLaunch;
}
didCompleteVideoPlayback() {
if (true === this.state.displayVideoPlayer) {
this.setState({
displayVideoPlayer: false
});
}
const currentRouteName = this.props.navigation.state.routeName;
if ('AppSplashScreen' !== currentRouteName) {
return false;
}
if (true === global.SKIP_SPLASH_SCREEN_REDIRECT) {
return false;
}
if (this.isFirstLaunch()) {
this.props.navigation.navigate('LanguageScreen');
return false;
}
this.props.navigation.navigate('HomeScreen');
}
render() {
return (
<View style={{flex: 1, backgroundColor: '#000000', alignItems: 'center', justifyContent: 'center'}}>
{true === this.state.displayVideoPlayer &&
<Video
source={VIDEO_SPLASH_2}
muted={true}
repeat={false}
playInBackground={false}
resizeMode="contain"
onEnd={() => this.didCompleteVideoPlayback()}
style={{height: '100%', width: '100%', backgroundColor: '#000000'}}
/>
}
</View>
);
}
}
My issue is, whenever I put the application in Background, and resume after 30 seconds, it always starts with AppSplashScreen whereas I expect it to resume from the last screen. It works correctly if I open it before 30 seconds. I assume somewhere it is killing the memory and starting the app from start when I resume after 30 second.
What could be the issue here. Or is there another workaround to resume the app in the same screen where the user left off.
I solved it by using State Persistence of react-navigation
Here is the documentation https://reactnavigation.org/docs/4.x/state-persistence/
Here is what my App.js look like now
import AsyncStorage from '#react-native-community/async-storage';
const App: () => React$Node = () => {
const persistenceKey = "navigationStatePersistenceKey"
const persistNavigationState = async (navState) => {
try {
await AsyncStorage.setItem(persistenceKey, JSON.stringify(navState));
} catch(err) {
// handle error
}
}
const loadNavigationState = async () => {
const jsonString = await AsyncStorage.getItem(persistenceKey);
return JSON.parse(jsonString);
}
return(
<View style={{flex: 1, backgroundColor: '#000000'}}>
<AppContainer
persistNavigationState={persistNavigationState}
loadNavigationState={loadNavigationState}
/>
</View>
);
};
It now takes user to the same screen where it was left off, no more restart from first screen.
I use a panResponder to create draggable view in my app. It's working fine on android but on iOS, the drag animation stops after moving la little bit.
Vidéo here
Here is my code :
export default class Draggable extends React.Component {
constructor(props) {
super(props);
const { pressDragRelease, pressDragStart, reverse, initPosition } = props;
this.state = {
pan: new Animated.ValueXY({
x: initPosition.dragX,
y: initPosition.dragY
}),
_value: { x: initPosition.dragX, y: initPosition.dragY }
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (gesture) => {
console.log("inside panResponder grant");
if (reverse === false) {
this.state.pan.setOffset({x: this.state._value.x,y: this.state._value.y});
this.state.pan.setValue({ x: 0, y: 0 });
} else {
this.state.pan.setValue({ x: gesture.dx, y: gesture.dy });
}
},
onPanResponderMove: Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
]),
//Called on android at the end
onPanResponderRelease: () => {
if (pressDragRelease) {
pressDragRelease({ x: this.state._value.x, y: this.state._value.y });
}
if (reverse === false) this.state.pan.flattenOffset();
else this.reversePosition();
},
//Called on ios at the end
onPanResponderTerminate: () => {
if (pressDragRelease) {
pressDragRelease({ x: this.state._value.x, y: this.state._value.y
});
}
if(reverse === false) {
this.state.pan.flattenOffset();
} else {
this.reversePosition();
}
}
});
}
componentWillMount() {
if (this.props.reverse === false)
this.state.pan.addListener(c => this.setState({ _value: c }));
}
componentWillUnmount() {
this.state.pan.removeAllListeners();
}
reversePosition = () => {
const { initPosition } = this.props;
Animated.spring(this.state.pan, {
toValue: { x: initPosition.dragX, y: initPosition.dragY }
}).start();
};
render() {
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout()]}
>
{this.props.children}
</Animated.View>
);
}
}
I'm using :
"react-native": "0.57.7"
I tried lots of things related to panResponder, as it works fine on android I guest it's an issue of handler or of the Animated.event in onPanResponderMove handler ?
Any help appreciated, I'm struggling on it for several days ! :)
Also ran into this issue.
Deleted my node modules and pods with a reinstall which was able to resolve it.
I know in the past react-navigation conflicted.
https://github.com/react-navigation/react-navigation/issues/5497
Actually ran into this while using a slider library which was built using panResponder.
With react-native, I implemented IOS CameraRoll that fetches 300 images from 'Camera Roll' Album on first and keep fetching 300 images whenever scroll reaches the end. Below is My code. SalmonCameraRoll.js
import React from 'react'
import {
View,
Text,
TouchableHighlight,
Modal,
StyleSheet,
Button,
CameraRoll,
Image,
Dimensions,
ScrollView,
FlatList,
} from 'react-native'
import Share from 'react-native-share';
import RNFetchBlob from 'react-native-fetch-blob';
let styles
const { width, height } = Dimensions.get('window')
const fetchAmount = 300;
class SalmonCameraRoll extends React.Component {
static navigationOptions = {
title: 'Salmon Camera Roll',
}
constructor(props) {
super(props);
this.state = {
photos: [],
// index: null,
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
refreshing: false,
};
this.tryGetPhotos = this.tryGetPhotos.bind(this);
this.getPhotos = this.getPhotos.bind(this);
this.appendPhotos = this.appendPhotos.bind(this);
this.renderImage = this.renderImage.bind(this);
this.onEndReached = this.onEndReached.bind(this);
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}
componentDidMount() {
this.subs = [
this.props.navigation.addListener('didFocus', () => {
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}),
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
tryGetPhotos = (fetchParams) => {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.getPhotos(fetchParams)})
}
}
getPhotos = (fetchParams) => {
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then(
r => this.appendPhotos(r)
)
}
appendPhotos = (data) => {
const photos = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (photos.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.photos = this.state.photos.concat(photos);
this.setState(nextState);
}
}
onEndReached = () => {
if (!this.state.noMorePhotos) {
this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'});
}
}
renderImage = (photo, index) => {
return (
<TouchableHighlight
style={{borderTopWidth: 1, borderRightWidth: 1, borderColor: 'white'}}
key={index}
underlayColor='transparent'
onPress={() => {
this.props.navigation.navigate('Camera', { backgroundImageUri: photo.node.image.uri })
}
}
>
<Image
style={{
width: width/3,
height: width/3
}}
representation={'thumbnail'}
source={{uri: photo.node.image.uri}}
/>
</TouchableHighlight>
)
}
render() {
return (
<View style={styles.container}>
<View style={styles.modalContainer}>
<FlatList
numColumns={3}
data={this.state.photos}
initialNumToRender={fetchAmount}
onEndReachedThreshold={500}
onEndReached={this.onEndReached}
refreshing={this.state.refreshing}
onRefresh={() => this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'})}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
this.renderImage(item, index)
)}
/>
</View>
</View>
)
}
}
styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
modalContainer: {
// paddingTop: 20,
flex: 1,
},
scrollView: {
flexWrap: 'wrap',
flexDirection: 'row'
},
shareButton: {
position: 'absolute',
width,
padding: 10,
bottom: 0,
left: 0
}
})
export default SalmonCameraRoll
Problem is that in circumstance of a lot of images(about 10000 images) in 'Camera Roll' album, each image component was loaded so slowly that it was also loaded too slowly when scrolling accordingly.
In other famous apps like Facebook or Instagram, it loads all images quickly at once without fetching whenever scroll reaches end.
How can i make my Image component load fast? Or best of all(if possible), how can i make my CameraRoll load all images quickly at once without fetching whenever scroll reaches end?
Thank you.
With this code how would I add a second or multiple panresponders that can be moved independently of each other? If I use the same panresponder instance and code they move together as one. I want to know how to have several independently draggable panresponders.
'use strict';
var React = require('react-native');
var {
PanResponder,
StyleSheet,
View,
processColor,
} = React;
var CIRCLE_SIZE = 80;
var CIRCLE_COLOR = 'blue';
var CIRCLE_HIGHLIGHT_COLOR = 'green';
var PanResponderExample = React.createClass({
statics: {
title: 'PanResponder Sample',
description: 'Shows the use of PanResponder to provide basic gesture handling.',
},
_panResponder: {},
_previousLeft: 0,
_previousTop: 0,
_circleStyles: {},
circle: (null : ?{ setNativeProps(props: Object): void }),
componentWillMount: function() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
});
this._previousLeft = 20;
this._previousTop = 84;
this._circleStyles = {
style: {
left: this._previousLeft,
top: this._previousTop
}
};
},
componentDidMount: function() {
this._updatePosition();
},
render: function() {
return (
<View
style={styles.container}>
<View
ref={(circle) => {
this.circle = circle;
}}
style={styles.circle}
{...this._panResponder.panHandlers}
/>
</View>
);
},
_highlight: function() {
const circle = this.circle;
circle && circle.setNativeProps({
style: {
backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)
}
});
},
_unHighlight: function() {
const circle = this.circle;
circle && circle.setNativeProps({
style: {
backgroundColor: processColor(CIRCLE_COLOR)
}
});
},
_updatePosition: function() {
this.circle && this.circle.setNativeProps(this._circleStyles);
},
_handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
// Should we become active when the user presses down on the circle?
return true;
},
_handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
// Should we become active when the user moves a touch over the circle?
return true;
},
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
this._highlight();
},
_handlePanResponderMove: function(e: Object, gestureState: Object) {
this._circleStyles.style.left = this._previousLeft + gestureState.dx;
this._circleStyles.style.top = this._previousTop + gestureState.dy;
this._updatePosition();
},
_handlePanResponderEnd: function(e: Object, gestureState: Object) {
this._unHighlight();
this._previousLeft += gestureState.dx;
this._previousTop += gestureState.dy;
},
});
var styles = StyleSheet.create({
circle: {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
backgroundColor: CIRCLE_COLOR,
position: 'absolute',
left: 0,
top: 0,
},
container: {
flex: 1,
paddingTop: 64,
},
});
module.exports = PanResponderExample;
You can use an array of PanResponders, created like so:
this._panResponders = yourObjectsArray.map((_, index) => (
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
...
})
));
yourObjectsArray is an array that you use for creating as many panResponders as you want, I imagine each object in that array will correspond to a data instance of whatever data structure you use to create the moveable Views.
Then to actually use it in your View:
render: function() {
return yourObjectsArray.map((_, index) => (
<View
style={styles.container}>
<View
... some stuff here ...
{...this._panResponders[index].panHandlers}
/>
</View>
)
};