I'm looking a way to make animated flatlist item after item. when one item finish his animation so next item(from the flatlist) will be on the screen
class AnimatedFlatList extends React.PureComponent {
state = {selected: (new Map(): Map<string, boolean>)};
let data = {[
{"first_name":"ltorrejon0#si.edu"},
{"first_name":"ichadbourne1#icq.com"},
{"first_name":"ascorthorne2#mediafire.com"},
{"first_name":"jlathwood3#xing.com"},
{"first_name":"molkowicz4#ftc.gov"},
{"first_name":"motridge5#tiny.cc"},
{"first_name":"rcess6#hostgator.com"},
{"first_name":"mmaundrell7#php.net"},
{"first_name":"ufairburne8#instagram.com"},
{"first_name":"pangel9#biglobe.ne.jp"}]
};
_keyExtractor = (item, index) => item.id;
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
selected.set(id, !selected.get(id)); // toggle
return {selected};
});
};
_renderItem = (item) => (
<View style={Styles.viewItem}}>
<Text style={Styles.textItem>{item.text}</Text>
</View>
);
render() {
return (
<FlatList
data={data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
When I did animatedView into the renderItem it runs all together and it not what I'm looking for.
Kind of this way (but without press on the button, it will load automatically)
Maybe this is not the best solution but I am using the delay property of Animated.timing() and it works well for me.
My item component looks like this:
export default class CustomItem extends Component {
constructor(props) {
super(props);
this.state = {
scaleValue: new Animated.Value(0)
}
}
componentDidMount() {
Animated.timing(this.state.scaleValue, {
toValue: 1,
duration : 600,
delay: this.props.index * 350
}).start();
}
render() {
return (
<Animated.View style={{ opacity: this.state.scaleValue }}>
{ this.props.children }
</Animated.View>
);
}
}
And here is the flatlist:
...
renderItem(item) {
return (
<CustomItem index={ item.index } >
<Text>{ item.first_name }</Text>
</CustomItem>
);
}
render() {
return (
<FlatList
keyExtractor={this._keyExtractor}
data={data }
renderItem={ this.renderItem.bind(this) }
/>
);
}
So, every single item will delay 350 milliseconds more than the item before it.
Of course, you can change the duration of the animation and the delay property and find the perfect animation for you :)
You need to be careful with the number of the items because you can wait too much time to see the last item :)
Checkout Animated.Stagger. Runs Animation parallelly but with successively specified delay.
Related
PLEASE NOTE that 'this' is not accessible from a static function: React Native : Access Component state inside a static function
I am trying to define a button in the screen's header that, when clicked, will affect rendering, and will be replaced with another icon.
It is an old app, still using react navigation 3.
I didn't know how to do the following things:
modify the component's state from a function that is activated when the button is pressed
modify the screen parameter from this function
What I managed to implement is the following lame and embarrassing way to do it:
When the button is clicked, a static function is executed, which modfies a static variable
periodic code is fired in componentDidMount that checks whether the static detailedDisplay variable has been modified. If it has been modified, this code sets a state variable that affects rendering. This periodic code also modifies the screen parameter which changes the icon in the header (because, as I wrote above, I also failed to set the parameter from the static function).
How can this be done in not-so-lame way?
Here is my code:
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
var detailedDisplay = false;
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
static toggleDisplay(navigation) {
detailedDisplay = !detailedDisplay;
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ detailedDisplay });
}
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
}
componentDidMount() {
setInterval(() => {
if (detailedDisplay !== this.state.detailedDisplay) {
this.props.navigation.setParams({ detailedDisplay });
this.setState({ detailedDisplay });
}
}, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}
I think you can try to use your state inside your navigationOption method
static navigationOptions = ({ navigation }) => {
return {
headerRight: //navigation.getParam('detailedDisplay', false) ?
this.state.detailedDisplay ?
<TouchableOpacity onPress={() => this.toggleDisplay(navigation)}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => this.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
And then change your state in the toggleDisplay method
static toggleDisplay(navigation) {
this.setState(state=> detailedDisplay:!state.detailedDisplay)
// the following statement gave the error "cannot read property
// 'setParams' of undefined", so I am setting it below.
// navigation.setParams({ this.state.detailedDisplay });
}
You can try and keep the param and a state variable in sync.
An example POC is here
Here, I have tried to keep the state toggleVariable and the param value in sync but updating them through a common setter, and on Component Mount it will just be the same as the parameter.
You cannot read properties of this because this in JS is dynamic, and when you pass function as callback this is lost. There is 2 solutions that i know:
One of them bind this using bind method:
constructor(props) {
super(props);
this.state = {
detailedDisplay: false,
};
this.toggleDisplay = this.toggleDisplay.bind(this);
this.navigationOptions = this.navigationOptions.bind(this)
}
Now you can use non-static methods and you can access react state and methods in them:
navigationOptions({ navigation }) {
// somewhere in code <button onPress={this.toggleDisplay}><button>
// this.props this.state this.setState is available here
};
toggleDisplay(navigation) {
// this.props this.state this.setState is available here
// this.props.navigation.setParams() is available too
}
Finally, you can change the detailedDisplay state and screen params in toggleDisplay function. So when you'll press button state'll be changed and your commponent'll be re-rendered.
Additional
Second way to do it is to call method in a function:
onPress={() => this.toggleDiaplay()}
Here you must also use non-static methods, and this will work the same way as the first solution with bind.
Try doing something like this:
UPDATED: removed static from the toggleDisplay and added Class reference in the static function.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/Octicons';
...
export default class Messages extends Component {
...
state = {
detailedDisplay: false,
};
...
static navigationOptions = ({ navigation }) => {
return {
headerRight: navigation.getParam('detailedDisplay', false) ?
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"check-circle"} />
</TouchableOpacity> :
<TouchableOpacity onPress={() => Messages.toggleDisplay()}>
<Icon name={"comment"} />
</TouchableOpacity>,
};
};
toggleDisplay() {
this.setState({detailedDisplay: !this.state.detailedDisplay})
this.props.navigation.setParams({detailedDisplay: true});
}
constructor(props) {
super(props);
}
componentDidMount() {
// setInterval(() => {
// if (detailedDisplay !== this.state.detailedDisplay) {
// this.props.navigation.setParams({ detailedDisplay });
// this.setState({ detailedDisplay });
// }
// }, 500);
}
...
return (
<View>
{ this.state.detailedDisplay ?
{this.renderConcise()} :
{this.renderDetailed()}
}
</View>
);
}
renderDetailed() {
...
}
renderConcise() {
...
}
}
What is the best pattern, in react native, to animate components on state change?
For example I have a list of elements and tapping on one I want it to disappear and the ones below him to 'get up' filling the missing space
How can I make the transition smooth?
React-natives own animated API works really well.
Basically you have a value in state, which you connect with a style props, and change that value over time. (for examples follow link)
For smooth animations use usenativedriver (not always possible) and also, make sure you don't have debugger runnning in emulated/real device
EDIT: 2018-05-31
This is an example of how I've used it. Probably exist other ways of doing it
import { Animated, Text} from 'react-native';
class ShowCaseAnimation extends Component {
state = {
animations: {
height: new Animated.Value(0),
fade: new Animated.Value(0),
}
}
componentDidMount() {
const { height, fade } = this.state.animations;
if (this.props.animate) {
doneAnimation({ height, fade }).start(() => {
// Do stuff after animations
});
}
}
render() {
const { animations } = this.state;
return (
<Animated.View
style={{
height: animate? animations.height : 300,
opacity: animate? animations.fade: 1,
// other styling
}}
>
<Text> All your base are belong to us </Text>
</Animated.View>
);
}
}
*doneAnimation: *
import { Animated, Easing } from 'react-native';
export const doneAnimation = ({ height, fade }) => Animated.parallel([
Animated.timing(height, {
toValue: 300,
easing: Easing.elastic(),
duration: 500,
delay: 1500,
}),
Animated.timing(fade, {
toValue: 1,
easing: Easing.ease,
duration: 1000,
delay: 1500,
}),
]);
export default doneAnimation;
doneAnimation will change the state and perform the described animations.
This is how you can trigger an animation on state change in a functional component.
Say you have a Button that changes state with onPress:
<Button title="toggle" onPress={() => setValue(!Value)} />
then you can trigger the animation inside a useEffect with the Value
that changes in the dependency array:
const [Value, setValue] = useState(true);
useEffect(() => {
// Input your animation here
// ...
}, [Value]);
I'm using React Native Lottie Wrapper to show animation on screen.
I need a functionality to play/pause/resume animation.
Here is my a part of my code:
...
constructor(props) {
super(props);
this.state = {
progress: new Animated.Value(0)
};
}
static navigationOptions = {
title: "Details",
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerTruncatedBackTitle: 'List'
};
componentDidMount() {
this.animation.play();
}
playLottie() {
console.log('play');
}
pauseLottie() {
console.log('pause');
}
render() {
return (
<View>
<Animation
ref={animation => { this.animation = animation; }}
source={require('../../../../assets/anim/balloons.json')}
style={{height: 300, width: '100%'}}
loop={false}
progress={this.state.progress}
/>
<Text>Course with id: {this.props.navigation.state.params.courseId}</Text>
<Button
onPress={this.playLottie}
title="Play Lottie"
color="#841584"
accessibilityLabel="Play video"
/>
<Button
onPress={this.pauseLottie}
title="Pause Lottie"
color="#841584"
accessibilityLabel="Pause video"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
...
The animation is playing well but I can't pause it and resume it.
Does anyone have a solution for this problem?
P.S. I have tried to use this.animation in pauseLottie() method but it said that is undefined.
Thank you in advance!
You can pause and play Lottie animation by changing the speed prop, where speed={0} puts LottieView component in pause and speed={1} plays it at normal speed.
Here is an example:
playAnimation = () => {
this.setState({speed: 1})
}
pauseAnimation = () => {
this.setState({speed: 0})
}
<LottieView
source={this.state.sourceAnimation}
speed={this.state.speed} />
You have to set the state from the play/pause functions. In order to access the state of the Component, you have to bind the function to the component class:
First option in your constructor:
constructor(props) {
super(props);
this.playLottie.bind(this);
this.pauseLottie.bind(this);
}
or second option when declaring inside class use the es6 function syntax:
playLottie = () => {
...
}
pauseLottie = () => {
...
}
Inside those function call setState and add the value you want to set it to. In your case I would:
playLottie = () => {
this.setState({ progress: true })
}
pauseLottie = () => {
this.setState({ progress: false })
}
It is important you bind those two functions to your class component, because you will not be able to access component props. Thats why it is throwing you an error setState is not a function
Your render looks good ;)
for me it didn't work well: we have to add setValue(0), then we need to improve pause/restart to maintain the playing speed and change easing function to avoid slow re-start. Let's also add looping:
constructor(props) {
super(props);
this.playLottie.bind(this);
this.pauseLottie.bind(this);
this.state = {
progress: new Animated.Value(0),
pausedProgress: 0
};
}
playLottie = () => {
Animated.timing(this.state.progress, {
toValue: 1,
duration: (10000 * (1 - this.state.pausedProgress)),
easing: Easing.linear,
}).start((value) => {
if (value.finished) this.restartAnimation();
});
}
restartAnimation = () => {
this.state.progress.setValue(0);
this.setState({ pausedProgress: 0 });
this.playAnimation();
}
pauseLottie = () => {
this.state.progress.stopAnimation(this.realProgress);
}
realProgress = (value) => {
console.log(value);
this.setState({ pausedProgress: value });
};
...
(Now) For me, it's working fine! Play and pause option work as expected.
If you use an Lottie animation that contains a loop you can control it all with the LottieView api built in. (if you are using a file that has the animation)
import Lottie from 'lottie-react-native'
const ref = useRef<AnimatedLottieView>()
const pause = () => {
ref.current.pause()
}
const resume = () => {
ref.current.resume()
}
const reset = () => {
ref.current.reset()
}
<Lottie
ref={ref}
source={source}
resizeMode={resizeMode}
loop={true}
duration={duration}
autoPlay={true}
onAnimationFinish={onFinish}
/>
I'm writing an image picker using react-native's CameraRoll API and rendering them in a FlatList inside CameraRollScreen component. This component takes a prop called maxPhotos, say 3, when a user has selected 3 photos, all other photos will be disabled (cannot be selected anymore), it looks like this (this is what I have right now, it works, but not performant):
As you can see, when I've selected 3 photos (which is the limit), all other photos are covered by a transparent view (disabled). This is not performant, doesn't seem so in the GIF, but when running on a real device, this problem can no longer be ignored. Selecting the first 2 photos doesn't cause any lag, however, upon selecting the last photo, since all other photos will have to be disabled, it becomes laggy. But I have no idea how else I could disable the other photos without disabling them 1 by 1. Here is the code I have for my image picker:
Since every image has different states, I also make each photo a PureComponent called CameraRollImage that has the following state:
{
uri: '',
index: -1 // if not selected, it's -1, if selected, it denotes
// the position of the photo in the 'selectedPhotos'
// array
disabled: false // Whether it should be disabled
}
CameraRollImage component:
class CameraRollImage extends PureComponent {
constructor(props) {
super(props);
this.state = {
uri: '',
index: -1,
disabled: false
};
this.onSelectPhoto = this.onSelectPhoto.bind(this);
}
componentWillMount() {
const { uri, index, disabled } = this.props;
this.setState({ uri, index, disabled });
}
componentWillReceiveProps(nextProps) {
const { uri, index, disabled } = nextProps;
this.setState({ uri, index, disabled });
}
onSelectPhoto() {
const { uri, index } = this.state;
this.props.onSelectPhoto({ uri, index });
// 'onSelectPhoto' is a method passed down to each photo
// from 'CameraRollScreen' component
}
render() {
const { uri, index, disabled } = this.state;
return (
<View style={{ ... }}>
<TouchableOpacity
disabled={disabled}
onPress={this.onSelectPhoto}
>
<Image
source={{ uri }}
style={{ ... }}
/>
</TouchableOpacity>
// If disabled, render a transparent view that covers the photo
{disabled && <View
style={{
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
width: ... height: ...
}}
/>}
// render the index here
</View>
);
}
}
export default CameraRollImage;
Then, in CameraRollScreen Component:
class CameraRollScreen extends Component {
constructor(props) {
super(props);
this.state = {
allPhotos: [], // all photos in camera roll
selectedPhotos: []
};
this.onSelectPhoto = this.onSelectPhoto.bind(this);
this.renderPhoto = this.renderPhoto.bind(this);
}
componentWillMount() {
// Access the photo library to grab all photos
// using 'CameraRoll' API then push all photos
// to 'allPhotos' property of 'this.state'
}
onSelectPhoto({ uri, index }) {
let { selectedPhotos } = { ...this.state };
if (index === -1) {
// this means that this photo is not selected
// and we should add it to 'selectedPhotos' array
selectedPhotos.push(uri);
} else {
_.pullAt(selectedPhotos, index);
}
this.setState({ selectedPhotos });
}
renderPhoto({ item }) {
// item is the uri of the photo
const { selectedPhotos } = this.state;
const index = _.indexOf(selectedPhotos, item);
// A photo should be disabled when reach the limit &&
// it's not selected (index === -1)
return (
<CameraRollImage
uri={item}
index={index}
onSelectPhoto={this.onSelectPhoto}
disabled={index === -1 && selectedPhotos.length >= 3}
/>
);
}
render() {
const { allPhotos } = this.state;
return (
<FlatList
data={allPhotos}
extraData={this.state}
...
...
numColumns={3}
renderItem={this.renderPhoto}
/>
);
}
}
export default CameraRollScreen;
I have only 100 photos in my photo library and it's already causing lags, many people have way way way more photos than I do, this way will cause disaster, but how should I go about updating so many photos in FlatList? Or, should I use FlatList at all?
Found the solution, thanks to Pir Shukarullah Shah and RaphaMex.
If I scroll down fast enough, many images were not rendered and they are being rendered when I reach them. This seems right, why render them anyway when they're not on the screen? What I did was that I made use of onViewableItemsChanged of FlatList:
<FlatList
...
...
keyExtractor={(item) => item} // This is important!!!
onViewableItemsChanged={this.onViewablePhotosChanged}
initialNumberToRender={Math.ceil(SCREEN_HEIGHT / IMAGE_SIZE) * 3}
...
/>
Then, onViewablePhotosChanged method:
onViewablePhotosChanged({ viewableItems }) {
let viewablePhotos = [];
viewableItems.forEach((item) => viewablePhotos.push(item.key));
this.setState({ viewablePhotos });
// Here, every object in 'viewableItems' has a key, which
// is the key you provided in 'keyExtractor={(item) => ...}',
// I used the 'uri' of each photo as the key, that's why
// I am pushing viewable photos' uri's to 'viewablePhotos' array
}
Lastly, modify the renderPhoto function to pass a viewable prop
renderPhoto({ item }) {
...
...
return (
<CameraRollImage
...
...
viewable={_.include(this.state.viewablePhotos, item)}
/>
);
}
Then, in CameraRollImage component, where we render images, there is a prop called viewable, if viewable === false, we simply do not update it:
componentWillReceiveProps(nextProps) {
const { ..., ..., viewable } = nextProps;
if (!viewable) {
this.setState({ viewable: false });
return;
}
...
...
}
BETTER YET!!! if viewable is false, instead of rendering the image, we render an equal-sized empty view, you know, to save memory, which of course doesn't seem to be important if there're only 100 photos:
render() {
if (!this.state.viewable) {
return (
<View
style={{
width={IMAGE_SIZE}
height={IMAGE_SIZE}
}}
/>
);
}
return (
<Image
...
...
/>
);
}
I'm using react native v0.49 and I'm trying to implement custom transition when navigate to other page.
what I'm trying to do is to make transition only for one scene from scene 2 to scene3. but not for all the app.
this example I found it's for all web so I want to make just for one screen and for all the app because if I do that way it will effect for all the app and it's not what I'm looking for. any idea?
class SceneOne extends Component {
render() {
return (
<View>
<Text>{'Scene One'}</Text>
</View>
)
}
}
class SceneTwo extends Component {
render() {
return (
<View>
<Text>{'Scene Two'}</Text>
</View>
)
}
}
let AppScenes = {
SceneOne: {
screen: SceneOne
},
SceneTwo: {
screen: SceneTwo
},
SceneThree: {
screen: SceneTwo
},
}
let MyTransition = (index, position) => {
const inputRange = [index - 1, index, index + 1];
const opacity = position.interpolate({
inputRange,
outputRange: [.8, 1, 1],
});
const scaleY = position.interpolate({
inputRange,
outputRange: ([0.8, 1, 1]),
});
return {
opacity,
transform: [
{scaleY}
]
};
};
let TransitionConfiguration = () => {
return {
// Define scene interpolation, eq. custom transition
screenInterpolator: (sceneProps) => {
const {position, scene} = sceneProps;
const {index} = scene;
return MyTransition(index, position);
}
}
};
class App extends Component {
return (
<View>
<AppNavigator />
</View>
)
}
Here's an example of how we do it, you can add your own transitions to make it your own. Our goal was simply to expose the baked-in transition configurations to have more control over the animations. Our transition configuration: https://gist.github.com/jasongaare/db0c928673aec0fba7b4c8d1c456efb6
Then, in your StackNavigator, add that config like so:
StackNavigator(
{
LoginScreen: { screen: LoginScreen },
HomeScreen: { screen: HomeScreen },
},
{
stateName: 'MainStack',
initialRouteName: 'HomeScreen',
initialRouteParams: { transition: 'fade' },
transitionConfig: TransitionConfig,
}
);
Finally, when you navigate, just add your params when you navigate:
this.props.navigation.navigate('HomeScreen', { transition: 'vertical' })