I want to animate this image carousel in reactNative, but have no idea how to start. Read the documentation about animations but still really stuck, have no idea how to incorporate it in. I tried it this way but keep getting a big fat error. Help!
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
this.props.images.length - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const {images} = this.props
const {selectedIndex} = this.state
return (
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
export default BackgroundCarousel;
Any help would be appreciated. Don't know where I'm going wrong. Basically trying to add a fade effect when my background carousel changes from image to image.
I have fixed your code and removed all errors, copy-paste it in https://snack.expo.io/ and give it some time to load.
Note: I have removed this.props.images for website demo, please change in your real project.
Working fade carousal: https://snack.expo.io/#rajrohityadav/fade-carosal
But I have not implemented this using React Animation.
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
export default class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
3 - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const images =[
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg','https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg']//this.props
const {selectedIndex} = this.state
return (
<>
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
</>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
You can also use a simple & optimized library react-native-fadecarousel and use it like this:
import React from 'react'
import { View, StyleSheet, Image } from 'react-native';
import FadeCarousel from 'react-native-fadecarousel';
const FadeCarouselScreen = () => {
const images = [
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg',
'https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg'
];
return <FadeCarousel
loop
fadeAnimationDuration={1000}
autoPlay={{enable: true , delay: 1000 }}>
{
images.map((image, index) => {
return <View key={`slide ${index}`} style={styles.slideItem}>
<Image style={styles.image} resizeMethod="resize" resizeMode="cover" source={{ uri: image }}/>
</View>
})
}
</FadeCarousel>
}
const styles = StyleSheet.create({
image: {
width: "100%",
height: 300
},
slideItem: {
width: "100%",
height: 300,
justifyContent: "center",
alignContent: "center"
}
})
export default FadeCarouselScreen
Related
How to create a slide down and up notification indicator in react native see below for and example
I would like the notification to slide down for about 3 seconds at the top of the screen and slide up back off the screen after 3 seconds
I tried this below but it is not working
const [stateAnimate, setAnimate] = useState(new Animated.Value(300));
const slideDown = () => {
Animated.spring(stateAnimate, {
toValue: 0,
}).start();
slideUp();
};
const slideUp = setTimeout(() => {
Animated.spring(stateAnimate, {
toValue: 0,
}).start();
clearTimeout(slideUp);
}, 3000);
<View>
<Animated.View style={[{ position: 'absolute', right: 0,left:0,backgroundColor: '#0400ff', height: '20%', width: '100%', }, { transform: [{ translateX: stateAnimate }] }]}>
<Text style={{ fontSize: 16, color: '#fff', alignSelf: 'center',marginTop:10 }}>loading please wait...</Text>
</Animated.View>
<View style={{ marginTop: 200 }}>
<Button
title="Slide"
onPress={() => {
slideDown();
}}
/>
</View>
</View>
I made a Toast like this in my app.
Toast.js:
import React from 'react';
import {Image, SafeAreaView, FlatList, TextInput, ScrollView, View, TouchableOpacity, Text} from 'react-native';
import * as Animatable from 'react-native-animatable';
import SafeArea, { type SafeAreaInsets } from 'react-native-safe-area'
export default class Toast extends React.Component {
singletonInstance = null;
state = {
visible: false,
title: "",
description: "",
backgroundColor: "",
textColor: ""
}
componentDidMount() {
Toast.singletonInstance = this;
this._isMounted = true;
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
this.setState({safeInsetTop: result.safeAreaInsets.top});
});
}
componentWillUnmount() {
this._isMounted = false;
this.setState = (state,callback)=>{
return;
};
}
static show(title, description, backgroundColor, textColor) {
Toast.singletonInstance._openPanel(title, description, backgroundColor, textColor);
}
_openPanel(title, description, backgroundColor, textColor) {
if (this._isMounted) {
this.setState({
visible: true,
title: title,
description: description,
backgroundColor: backgroundColor,
textColor: textColor
});
this.setCloseTimer()
} else {
this._isMounted = true;
}
};
close = () => {
if (this._isMounted) {
if (this.view != null) {
this.view.fadeOutUp().then(endState =>
this.setState({visible: false})
);
} else {
this.setState({visible: false})
}
}
};
setCloseTimer() {
this.closeTimer = setTimeout(() => {
this.close()
}, 4000);
}
handleViewRef = ref => this.view = ref;
render() {
if (this.state.visible) {
return (
<View style={{width: '100%', position: 'absolute', top: 0, left: 0, right: 0}}>
<Animatable.View ref={this.handleViewRef} animation="slideInDown" style={{flex: 1, paddingTop: this.state.safeInsetTop, paddingHorizontal: 20, paddingBottom: 20, backgroundColor: this.state.backgroundColor}} >
<Text ellipsizeMode='tail' numberOfLines={1} style={{fontFamily: "Nunito-ExtraBold", fontSize: 12, width: '100%', marginLeft: 6, marginTop: 4, color: this.state.textColor}}>{this.state.title}</Text>
<Text ellipsizeMode='tail' style={{fontFamily: "Nunito-Bold", fontSize: 18, width: '100%', marginLeft: 6, marginTop: 4, color: this.state.textColor}}>{this.state.description}</Text>
</Animatable.View>
</View>
)
} else {
return null;
}
}
}
And then I use it in my other files like this:
...
import Toast from '../components/Toast.js'
...
...
Toast.show("Warning!", "You are about to delete everything", "white", "red");
...
I'm sure there is a prettier solution or a module somewhere, but it works for me :-)
You can use react-native-paper
Banner- React Native Paper
import React from 'react';
import { Banner } from 'react-native-paper';
const MyComponent = () => {
const [visible, setVisible] = React.useState(true);
return <Banner
visible={visible}
actions={[
{
label: 'Fix it',
onPress: () => setVisible(false),
},
{
label: 'Learn more',
onPress: () => setVisible(false),
},
]}
icon={({size}) => (
<Image
source={{
uri: 'https://avatars3.githubusercontent.com/u/17571969?s=400&v=4',
}}
style={{
width: size,
height: size,
}}
/>
)}>
There was a problem processing a transaction on your credit card.
</Banner>
}
I'm really sorry to bother you, but I try to learn React-Native. I'm trying to make a weather app and I have this error :
Text strings must be rendered within a <Text> component.
- node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4137:8 in <anonymous>
- node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4134:2 in createTextInstance
- ... 9 more stack frames from framework internal
Here's are my two files of code:
weather-card.js
import React, {Component} from "react";
import {Animated, View, Text, PanResponder, Image} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
const CARD_INITIAL_POSITION_Y = hp("80%");
const CARD_INITIAL_POSITION_X = wp("5%");
const TRESHOLD_TO_TOP = hp("75%");
const TRESHOLD_TO_BOTTOM = hp("70%");
const CARD_OPEN_POSITION = hp("45%");
const MAX_DRAG_ZONE_WHEN_OPEN = hp("65%");
const ICON_URL = "http://openweathermap.org/img/w/";
class WeatherCard extends Component {
state = { panResponder : undefined, isOpen: false};
componentDidMount() {
this.position = new Animated.ValueXY();
this.position.setValue({x:CARD_INITIAL_POSITION_X, y: CARD_INITIAL_POSITION_Y});
const panResponder = PanResponder.create({
onStartShouldSetPanResponder:() => true,
onPanResponderMove:(e,gesture) => {
if(!(this.state.isOpen && gesture.y0 > MAX_DRAG_ZONE_WHEN_OPEN)) {
this.position.setValue({
x:CARD_INITIAL_POSITION_X,
y: gesture.moveY
});
}
},
onPanResponderRelease:(e, gesture) => {
if (!this.state.isOpen){
if(gesture.moveY <= TRESHOLD_TO_TOP) {
this.setOpenPosition(() => this.setState({isOpen: true}));
} else {
this.resetPosition();
}
} else {
if(gesture.moveY <= TRESHOLD_TO_BOTTOM){
this.setOpenPosition()
} else {
if(gesture.y0 < MAX_DRAG_ZONE_WHEN_OPEN){
this.resetPosition(() => this.setState({isOpen: false}))
}
}
}
}
});
this.setState({panResponder})
}
setOpenPosition = (done) => {
Animated.spring(this.position, {
toValue: {x: CARD_INITIAL_POSITION_X, y : CARD_OPEN_POSITION}
}).start(() => done && done());
};
resetPosition = (done) => {
Animated.spring(this.position, {
toValue: {x: CARD_INITIAL_POSITION_X, y : CARD_INITIAL_POSITION_Y}
}).start(() => done && done())
};
getCardStyle() {
return {
width: wp("90%"),
height: hp("110%"),
borderRadius: 10,
zIndex: 2,
backgroundColor: "white",
elevation: 1,
shadowColor: "black",
shadowOpacity: 0.2,
shadowOffset: { height: 2, width: 2 },
position: "absolute",
left: CARD_INITIAL_POSITION_X,
padding: hp("2%"),
...this.position.getLayout()
};
}
renderHeader(){
return (
<View
style={{
justifyContent: "center",
alignItems: "center"
}}
>
<Text style={{ fontSize: 30, marginBottom: hp("1%") }}>
{this.props.currentWeather.name}
</Text>
<View style={{ flexDirection: "row" }}>
<Text style={{ marginTop: hp("1%"), fontSize: 35 }}>
{this.props.currentWeather.main.temp}
</Text>
<Image
style={{ height: 60, width: 60 }}
source={{
uri: `${ICON_URL}${this.props.currentWeather.weather[0].icon}.png`
}}
/>
</View>
</View>
)
}
render() {
return this.state.panResponder ? <Animated.View {...this.state.panResponder.panHandlers} style={this.getCardStyle()}> {this.renderHeader()} </Animated.View> : <View />;
}
}
export default WeatherCard;
And search-screen.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import MapView from 'react-native-maps';
import {SearchBar} from "react-native-elements";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import {connect} from "react-redux";
import {getCurrentWeatherByCity} from "../actions/index";
import WeatherCard from "../components/weather-card";
const DEFAULT_COORD = {
lat: 48.859268,
lng: 2.347060
};
class SearchScreen extends React.Component {
state={search: ""};
updateSearch = search => {
this.setState({search})
};
submitSearch = () => {
this.props.getCurrentWeatherByCity(this.state.search);
console.log(this.state.search)
};
render() {
console.log(this.props.currentWeather);
return (
<View style={styles.container}>
<MapView style={{flex : 1}}
region={{latitude : this.props.currentWeather ? this.props.currentWeather.coord.lat : DEFAULT_COORD.lat, longitude : this.props.currentWeather ? this.props.currentWeather.coord.lon : DEFAULT_COORD.lng, latitudeDelta: 0.2000, longitudeDelta: 0.1000}}
scrollEnabled={false}
liteMode={true} />
{this.props.currentWeather && <WeatherCard currentWeather={this.props.currentWeather} />}
<SearchBar
lightTheme
onChangeText={this.updateSearch}
value={this.state.search}
onSubmitEditing={this.submitSearch}
containerStyle={{
position: "absolute",
bottom: hp("50%"),
left: wp("5%"),
width: wp("90%")
}}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
const mapStateToProps = (store) => {
return {
currentWeather : store.weather.currentWeather
}
};
const mapDispatchToProps = {
getCurrentWeatherByCity
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchScreen)
I was asking myself if the problem could be :
{this.props.currentWeather && <WeatherCard currentWeather={this.props.currentWeather} />}
Because it's not inside a Text component...
If you can help, it could be really great :D
If you need other resources, do not hesitate!
Thanks in advance :)
It might be because of this try this:
change
{this.props.currentWeather.name}
to
{this.props.currentWeather.name != undefined ? this.props.currentWeather.name : null}
Similarly do the above thing for {this.props.currentWeather.main.temp}
Hope this helps!
I would like to post my answer if someone has the same problem. The error says that a text string is "outside" a component. However it wasn't the case. It was indentation's problem. So don't forget to pay attention about that :D Expo can be mean !
I am trying to achieve an image/video carousel using https://jossmac.github.io/react-images/
and it should be like this including modal :
I following the code snippet given there but it's not working and I don't see any step by step guide to making that carousel.
class Gall extends Component {
state = { modalIsOpen: false }
toggleModal = () => {
this.setState(state => ({ modalIsOpen: !state.modalIsOpen }));
}
render() {
const { modalIsOpen } = this.state;
return (
<ModalGateway>
{modalIsOpen ? (
<Modal onClose={this.toggleModal}>
<Carousel views={images} />
</Modal>
) : null}
</ModalGateway>
);
}
}
export default Gall;
can anyone please help with a codesandbox?
Also is it possible to trigger the modal with the current active image?
Thanks in advance.
There is a link in their docs to the source
// #flow
// #jsx glam
import glam from 'glam';
import React, { Component, Fragment } from 'react';
import { type ProviderProps } from '../../ImageProvider';
import Carousel, { Modal, ModalGateway } from '../../../src/components';
import { FooterCaption } from '../components';
import { getAltText } from '../formatters';
type State = {
selectedIndex?: number,
lightboxIsOpen: boolean,
};
export default class Home extends Component<ProviderProps, State> {
state = {
selectedIndex: 0,
lightboxIsOpen: false,
};
toggleLightbox = (selectedIndex: number) => {
this.setState(state => ({
lightboxIsOpen: !state.lightboxIsOpen,
selectedIndex,
}));
};
render() {
const { images, isLoading } = this.props;
const { selectedIndex, lightboxIsOpen } = this.state;
return (
<Fragment>
{!isLoading ? (
<Gallery>
{images.map(({ author, caption, source }, j) => (
<Image onClick={() => this.toggleLightbox(j)} key={source.thumbnail}>
<img
alt={caption}
src={source.thumbnail}
css={{
cursor: 'pointer',
position: 'absolute',
maxWidth: '100%',
}}
/>
</Image>
))}
</Gallery>
) : null}
<ModalGateway>
{lightboxIsOpen && !isLoading ? (
<Modal onClose={this.toggleLightbox}>
<Carousel
components={{ FooterCaption }}
currentIndex={selectedIndex}
formatters={{ getAltText }}
frameProps={{ autoSize: 'height' }}
views={images}
/>
</Modal>
) : null}
</ModalGateway>
</Fragment>
);
}
}
const gutter = 2;
const Gallery = (props: any) => (
<div
css={{
overflow: 'hidden',
marginLeft: -gutter,
marginRight: -gutter,
}}
{...props}
/>
);
const Image = (props: any) => (
<div
css={{
backgroundColor: '#eee',
boxSizing: 'border-box',
float: 'left',
margin: gutter,
overflow: 'hidden',
paddingBottom: '16%',
position: 'relative',
width: `calc(25% - ${gutter * 2}px)`,
':hover': {
opacity: 0.9,
},
}}
{...props}
/>
);
I am trying with react native , I have studied about it and now I am trying to learn it , This is my first day in this , I got some hints that how does it work form bellow mentioned tutorial.
https://www.tutorialspoint.com/react_native/react_native_text_input.htm
I am just trying with login page and handling the submit button click, just want to capture input data and want to app it to my Button.js file.
I know it may looks silly to you all there, but I really want to know it , I studied about props and was trying to use that in the same but when I replace component with props I start getting red screen , Please guide me little on this that if in case we are having two different components then how do we can pass our data between them.
Here I am posting both of the JS files :-
Form.js
import React, { Component, PropTypes } from 'react';
import Dimensions from 'Dimensions';
import {
StyleSheet,
KeyboardAvoidingView,
View,
ActivityIndicator,
TouchableOpacity,
Image,
} from 'react-native';
import UserInput from './UserInput';
import ButtonSubmit from './ButtonSubmit';
import SignupSection from './SignupSection';
import usernameImg from '../images/username.png';
import passwordImg from '../images/password.png';
import eyeImg from '../images/eye_black.png';
export default class Form extends Component {
constructor(props) {
super(props);
this.state = {
showPass: true,
press: false,
username: '',
password: ''
};
this.showPass = this.showPass.bind(this);
this.handleChange = this.handleChange.bind(this);
}
showPass() {
this.state.press === false ? this.setState({ showPass: false, press: true }) :this.setState({ showPass: true, press: false });
}
handleChange(event) {
// this.setState({usernamevalue: event.target.usernamevalue , passwordvalue : event.target.passwordvalue });
alert('A name was submitted: ' + this.state.password);
}
render() {
return (
<KeyboardAvoidingView behavior='padding'
style={styles.container}>
<UserInput source={usernameImg}
placeholder='Username'
autoCapitalize={'none'}
returnKeyType={'done'}
value={this.state.username}
onChangeText={(text) => this.setState({username:text})}
autoCorrect={false} />
<UserInput source={passwordImg}
secureTextEntry={this.state.showPass}
placeholder='Password'
returnKeyType={'done'}
value={this.state.password}
onChangeText={(text) => this.setState({password:text})}
autoCapitalize={'none'}
autoCorrect={false} />
<TouchableOpacity
activeOpacity={0.7}
style={styles.btnEye}
onPress={this.handleChange}
>
<Image source={eyeImg} style={styles.iconEye} />
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
}
const DEVICE_WIDTH = Dimensions.get('window').width;
const DEVICE_HEIGHT = Dimensions.get('window').height;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
},
btnEye: {
position: 'absolute',
top: 55,
right: 28,
},
iconEye: {
width: 25,
height: 25,
tintColor: 'rgba(0,0,0,0.2)',
},
});
ButtonSubmit.JS
import React, { Component, PropTypes } from 'react';
import Dimensions from 'Dimensions';
import {
StyleSheet,
TouchableOpacity,
Text,
Animated,
Easing,
Image,
Alert,
View,
} from 'react-native';
import { Actions, ActionConst } from 'react-native-router-flux';
import spinner from '../images/loading.gif';
const DEVICE_WIDTH = Dimensions.get('window').width;
const DEVICE_HEIGHT = Dimensions.get('window').height;
const MARGIN = 40;
export default class ButtonSubmit extends Component {
constructor() {
super();
this.state = {
isLoading: false,
};
this.buttonAnimated = new Animated.Value(0);
this.growAnimated = new Animated.Value(0);
this._onPress = this._onPress.bind(this);
}
_onPress() {
if (this.state.isLoading) return;
this.setState({ isLoading: true });
Animated.timing(
this.buttonAnimated,
{
toValue: 1,
duration: 200,
easing: Easing.linear
}
).start();
setTimeout(() => {
this._onGrow();
}, 2000);
setTimeout(() => {
Actions.secondScreen();
this.setState({ isLoading: false });
this.buttonAnimated.setValue(0);
this.growAnimated.setValue(0);
}, 2300);
}
_onGrow() {
Animated.timing(
this.growAnimated,
{
toValue: 1,
duration: 200,
easing: Easing.linear
}
).start();
}
render() {
const changeWidth = this.buttonAnimated.interpolate({
inputRange: [0, 1],
outputRange: [DEVICE_WIDTH - MARGIN, MARGIN]
});
const changeScale = this.growAnimated.interpolate({
inputRange: [0, 1],
outputRange: [1, MARGIN]
});
return (
<View style={styles.container}>
<Animated.View style={{width: changeWidth}}>
<TouchableOpacity style={styles.button}
onPress={this._onPress}
activeOpacity={1} >
{this.state.isLoading ?
<Image source={spinner} style={styles.image} />
:
<Text style={styles.text}>LOGIN</Text>
}
</TouchableOpacity>
<Animated.View style={[ styles.circle, {transform: [{scale: changeScale}]} ]} />
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
top: -95,
alignItems: 'center',
justifyContent: 'flex-start',
},
button: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F035E0',
height: MARGIN,
borderRadius: 20,
zIndex: 100,
},
circle: {
height: MARGIN,
width: MARGIN,
marginTop: -MARGIN,
borderWidth: 1,
borderColor: '#F035E0',
borderRadius: 100,
alignSelf: 'center',
zIndex: 99,
backgroundColor: '#F035E0',
},
text: {
color: 'white',
backgroundColor: 'transparent',
},
image: {
width: 24,
height: 24,
},
});
Should I use something like this.props.state.username to pass data to SubmitButton.js file.
The above code is from :- https://github.com/dwicao/react-native-login-screen
I am playing with this demo to understand the flow and concepts, Please provide me some suggestions here.
Your little help would be very much appreciated
Regards.
In the Component ProgressBarAndroid, there are props indeterminable={Boolean} which show to a user an animation of what it's going on. I would like to do almost the same on ProgressViewIOS. So I tried to Animate it with Animated...
I saw on docs of Animated method called 'createAnimatedComponent' which they use to create Animated.View
I tried so to create another Animated (Native) Component but it doesn't work at all.
The animation should gradually raise fillValue to 20 % and continue with an original value from the media upload...
This is my Component
// ProgressBar.ios.js
// #flow
import { PropTypes } from 'react';
import Component from 'components/base/Component';
import { ProgressViewIOS, Animated } from 'react-native';
const AnimatedProgressViewIOS = Animated.createAnimatedComponent(ProgressViewIOS);
class ProgressBarIOS extends Component {
static propTypes = {
// Percentage (0 - 100)
fill: PropTypes.number.isRequired,
};
constructor(props, context: any) {
super(props, context);
this.state = {
fillValue: new Animated.Value(props.fill),
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.fill === 0) {
Animated.timing(this.state.fillValue, { toValue: 0.2, duration: 500 }).start();
} else if (nextProps.fill > 19) {
this.state.fillValue.setValue(nextProps.fill / 100);
}
}
shouldComponentUpdate(nextProps) {
return this.props.fill !== nextProps.fill;
}
render() {
return (
<AnimatedProgressViewIOS
style={{ alignSelf: 'stretch' }}
progress={this.state.fillValue} />
);
}
}
export default ProgressBarIOS;
EDIT: AnimatedComponent is used to modify style only. Props could be passed as animated value but remember it is not a number!
Animated.createAnimatedComponent can animate a number of different properties, however only some properties are supported using the native driver, fortunately it appears progress on ProgressViewIOS is one of them.
Here is a working implementation of an animated ProgressViewIOS.
import * as React from 'react';
import { View, SafeAreaView } from 'react-native';
import { ProgressViewIOS, Animated } from 'react-native';
const AnimatedProgressViewIOS = Animated.createAnimatedComponent(
ProgressViewIOS
);
export default function App() {
const value = React.useRef(new Animated.Value(0));
React.useEffect(() => {
Animated.loop(
Animated.timing(value.current, {
duration: 2000,
toValue: 1,
useNativeDriver: true,
})
).start();
}, []);
return (
<SafeAreaView>
<View style={{ padding: 20 }}>
<AnimatedProgressViewIOS
style={{ alignSelf: 'stretch' }}
progress={value.current}
/>
</View>
</SafeAreaView>
);
}
It's worth noting that ProgressViewIOS is now deprecated, but building your own progress view is very straight forward and requires just two Views with simple styling like this (expo snack):
import * as React from 'react';
import { View, SafeAreaView, StyleSheet, Button, Text } from 'react-native';
import { Animated } from 'react-native';
export default function App() {
const [progress, setProgress] = React.useState(() => Math.random());
return (
<SafeAreaView>
<View style={{ padding: 20 }}>
<AnimatedProgressView progress={progress} />
<Text style={{padding: 20, textAlign: 'center'}}>{Math.round(progress * 100)}%</Text>
<Button title="Animate" onPress={() => setProgress(Math.random())} />
</View>
</SafeAreaView>
);
}
function AnimatedProgressView({ progress, style }) {
const value = React.useRef(new Animated.Value(0));
const [width, setWidth] = React.useState(0);
React.useEffect(() => {
Animated.spring(value.current, { toValue: progress }).start();
}, [progress]);
return (
<View
style={[styles.track, style]}
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}>
<Animated.View
style={[
styles.fill,
{
transform: [
{
translateX: value.current.interpolate({
inputRange: [0, 1],
outputRange: [-width, 0],
overflow: 'clamp',
}),
},
],
},
]}
/>
</View>
);
}
const styles = StyleSheet.create({
track: {
minHeight: 4,
borderRadius: 2,
overflow: 'hidden',
backgroundColor: '#ddd',
},
fill: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'blue',
},
});