In React, it seems like an animation is always bound to a property in this.state. But what if I have multiple objects in a view that need to be animated? For example what if I have a ListView with multiple Images and I want to animate the opacity of those images as they load into the ListView?
render() {
//var _scrollView: ScrollView;
return (
<View style={styles.container}>
<ScrollView
style={styles.scrollView}>
<ListView
initialListSize={1}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
style={styles.postList}
/>
</ScrollView>
</View>
);
}
renderRow(post) {
//var postItemHeight = windowSize / 2
return (
<View style={styles.postItem}>
<Image
style={styles.postImage}
source={{uri: post.cover_image}}
onLoad={(e) => this.imageLoaded()}>
</Image>
</View>
);
}
imageLoaded() {
// now animate the image opacity
// for every image that is loaded into the listview
}
There is no reason the Animated value needs to live in component state - that's just how the examples show how to do it. If you wanted, you can keep an array of Animated values in the state, put them in your Flux store, or however you want to do it.
In your particular case, however, the easiest solution would be create a component that represents a single image row of your ListView. You can then use that component's individual state to manage its animation.
For example:
const FadeImage = React.createClass({
displayName: 'FadeImage',
propTypes: Image.propTypes,
getInitialState() {
return {
opacity: new Animated.Value(0)
};
},
setNativeProps(nativeProps) {
this._image.setNativeProps(nativeProps);
},
fadeIn() {
Animated.spring(this.state.opacity, {
toValue: 1,
friction: 10,
tension: 60
}).start();
},
render() {
return (
<Animated.View style={{opacity: this.state.opacity}}>
<Image {...this.props} onLoad={this.fadeIn} ref={component => this._image = component} />
</Animated.View>
);
}
});
It's a drop-in replacement for Image, so you can use it just as you would a regular Image component.
Related
I am confused about how to properly dynamically add/create same components on button press for react native. I have used .map(()=>{}) on existing info to create components and then display the results.
Would I have to save each new component into a setstate array, then map that?
I looked a little into refs, but wasn't sure how that was better than just a setstate. The problem I see is if I want to update the value for each component, how would I go about that if their all originally duplicates?
Something along the lines of this:
class SingleExercise extends Component {
constructor(props) {
super(props);
this.state = {
objInfo: this.props.navigation.getParam("obj"),
currentSetSelected: 0
};
this.addSet = this.addSet.bind(this);
}
addSet = () => {
return (
<addSet />
)
}
render() {
const { navigation } = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ height: 80 }}>
<addSet />
<View>
<Button //here
large={false}
onPress={() => {
this.addSet();
}}
title={"add more"}
/>
</View>
</View>
</View>
);
}
}
const addSet = () => {
return (
<TouchableHighlight>
<View>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
defaultValue={'test'}
onChangeText={(text) => this.setState({text})}
/>
</View>
</TouchableHighlight>
);
}
Here is what I would do:
Every click on addSet button should increment the AddSets counter like this:
<Button
large={false}
onPress={() => {
this.setState({addSetsCounter: this.state.addSetsCounter});
}}
title={"add more"}
/>
After every state update your component will be re-rendered. So now, all you need to do is to forLoop in through that counter and return as many AddSets components as needed. A simple for loop with .push() inside would do.
Inside render, before return place something like that:
let sets =[];
for(let i =0;i<this.state.addSetsCounter;i++){
sets.push(<AddSets key="AddSets-{i}"/>);
}
Then simply render your {sets}.
I cannot fully test that right now, I wrote that from the top of my head, just play with the above, at least I hope it points you in a right direction.
I'm having an issue with the FlatList component when calling scrollToIndex. Here is how I'm referencing the list.
<Modal visible={modal} transparent onRequestClose={this.onClose}>
<TouchableWithoutFeedback onPress={this.onClose}>
<View style={overlayStyle}>
<Animated.View style={[styles.picker, pickerStyle]}>
<FlatList
ref={(node) => { this.scroll = node; }}
style={styles.scroll}
data={data}
renderItem={this.renderItems}
getItemLayout={(_, index) => (
{ length: 24, offset: 24 * index, index }
)}
initialNumToRender={5}
// scrollEnabled={visibleItemCount < itemCount}
contentContainerStyle={styles.scrollContainer}
keyExtractor={(item) => item.value}
automaticallyAdjustContentInsets={false}
removeClippedSubviews={false}
indicatorStyle="white"
/>
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Modal>
I included the surrounding code in case that would help. But for some reason I get back a different structure than normal with an error that says _scrollRef.scrollToIndex is undefined. When I check the tree and the ref component I seem to get back the right structure of _listRef -> _scrollRef but the contents are of that of a view ref or a textinput ref. Looking something like...
blur
context
focus
measure
measureInWindow
measureLayout
props
refs
setNativeProps
state
updater
I'm extremely confused on this situation and some insight as to why would be great.
EDIT:
Here is the code for the scrollToIndex. The promise method was recommended by someone in the RN community. I was using just a set timeout previously.
const wait = new Promise((resolve) => setTimeout(resolve, 500));
wait.then(() => {
if (this.mounted) {
console.log(this.scroll);
if (this.scroll) {
this.scroll
.scrollToIndex({ index: 0, animated: false });
}
Animated
.timing(opacity, {
duration: animationDuration,
toValue: 1,
})
.start(() => {
// if (this.mounted && Platform.OS === 'ios') {
// this.scroll.flashScrollIndicators();
// }
});
}
});
I have a class and in that class I am displaying carousel components like this
sampleclass.js
.
.
.
render{
return (
<Image ..>
<Text>
text
</Text>
<Image ..>
<js file tag where carousel is defined />
</Image>
<Text>
text
</Text>
<Image ..>
<js file tag where carousel is defined />
</Image>
.
.
.
</Image>
);
}
function fname(params..){
I also need to access carousel ref here
}
and I have another class where I have defined the carousel
carouselclass.js
import React, { PureComponent } from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import Carousel from 'react-native-snap-carousel';
export default class ExampleCarousel extends PureComponent {
state = {
data: [{}, {}, {}, {}, {}, {}]
}
renderItem = () => (
<View style={styles.tile} />
);
render () {
return (
<View style={styles.container}>
<Carousel
data={this.state.data}
renderItem={this.renderItem}
itemWidth={Dimensions.get('window').width * 0.85}
sliderWidth={Dimensions.get('window').width}
containerCustomStyle={styles.carousel}
slideStyle={{ flex: 1 }}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
height: 300
},
carousel: {
flex: 1,
backgroundColor: 'red'
},
tile: {
flex: 1,
width: Dimensions.get('window').width * 0.85,
backgroundColor: 'yellow'
}
});
I have to handle the onPress event of the carousel swipe components in the function of sampleclass.js
How can I do this I know how to handle onPress events on react native but couldnt do this with react-native-snap-carousel since I am using it for the first time I have read examples given in the docs but couldnt find something related to this
If you want to handle single Carousel item's onPress prop, you need to add that to your render item.
Example
// change this
renderItem = () => (
<View style={styles.tile} />
);
// to this
_onPressCarousel = () => {
// here handle carousel press
}
renderItem = () => (
<TouchableOpacity onPress={this._onPressCarousel} style={styles.tile}>
<Image
style={styles.button}
source={require('./myButton.png')}
/>
</TouchableOpacity>
);
<Carousel
...
renderItem={(carousel, parallaxProps) =>
this._renderItem(carousel, parallaxProps)
}
...
/>
_renderItem(carousel, parallaxProps) {
const { item } = carousel;
return (
<TouchableOpacity
onPress={() => {
this._handlePress(item._id);
}}
>
...
I've recently been learning React Native but haven't really found a way to manipulate the DOM.
I've been trying to make it so that if I click on the TouchableHighlight my Image moves down a couple of px, but I have not managed to get it to move yet and I honestly don't know how to go from here.
My onclick function works since it does return the log every time the button is clicked.
As of now I have this:
export default class MainBody extends Component {
onclick = () => {
console.log('On click works')
};
render() {
return (
<ScrollView showsHorizontalScrollIndicator={false} style={Style.horList} horizontal={true}>
<View >
{/*i need to move this Image down!*/}
<Image source={require("./img/example.png")}/>
<View>
<Text style={Style.peopleInvited}>This is text</Text>
</View>
{/*When clicked on this touchable highlight!*/}
<TouchableHighlight onPress={this.onclick}}>
<Image source={require('./img/moveImg.png')}/>
</TouchableHighlight>
</View>
</ScrollView>
}
If someone could help me get past this that would be greatly appreciated.
Thank you so much for your time!
There are many ways to do this, but perhaps the easiest way would be using states.
class MainBody extends Component {
constructor(props) {
super(props);
this.state = {
top: 0 // Initial value
};
}
onclick = () => {
console.log('On click works')
this.setState( { top: this.state.top + 5 }) // 5 is value of change.
};
render() {
return (
<ScrollView showsHorizontalScrollIndicator={false} horizontal={true}>
<View >
{/*i need to move this Image down!*/}
<Image style={{top: this.state.top}}source={require("./img/example.png")}/>
<View>
<Text>This is text</Text>
</View>
{/*When clicked on this touchable highlight!*/}
<TouchableHighlight onPress={this.onclick}>
<Image source={require("./img/moveImg.png")}/>
</TouchableHighlight>
</View>
</ScrollView>
);
}
}
What are the best practices for rendering an image based newsfeed in react native?
I've got a very simple newsfeed where each story has a cover image and then a star rating (composed of 1-5 images). On initial render, the images appear to be loaded in random order and it looks awful and non-native.
Here's a 7s video showing the screen transition and render.
Is there any way to control the order in which the images render, or not to render a story until the whole block is loaded?
If it helps, the code is below. Using IncrementalListView to render the rows. Release build, iOS 9.3, iPhone 6. Each cover image is ~55kb JPG, and the star is ~3kb PNG. Both images are packaged into the bundle.
UPDATE 3/31
I changed the code to use IncrementalListView instead of rendering directly into ScrollView, but this hasn't helped. The problem seems to be with how images are decoded and rendered, not with how rows are rendered.
class DiscoverRow extends React.Component {
render() {
let images = {
wide: {
data: require('../img/img-wide.jpg'),
height: 200,
width: 376
}
};
let title = this.props.event.name;
let date = "Tomorrow";
let place = this.props.event.venue.name;
const newHeight = images.wide.height / images.wide.width * screenWidth;
return (
<View style={[rowStyles.cell]}>
<View style={{ borderRadius: 15 }}>
<Image resizeMode={'stretch'} source={images.wide.data} style={[rowStyles.thumbnail]} />
<View style={[rowStyles.annotationsContainer]}>
<View style={rowStyles.textContainer}>
<AHStarRating starColor={gConstants.themeColor} disabled rating={4.5} style={{ width: 100 }} />
<AHText style={rowStyles.title}>{title}</AHText>
<AHText style={rowStyles.date}>{date}</AHText>
</View>
<View style={rowStyles.commentsContainer}>
<Image source={require('../img/chat.png')}
style={{ width: 36, height: 36,
tintColor: gConstants.themeColor,
backgroundColor: 'transparent' }}
/>
<TouchableWithoutFeedback onPress={this._poop}>
<Image
source={require('../img/heart.png')}
style={{ width: 36, height: 36,
tintColor: gConstants.themeColor,
backgroundColor: 'transparent' }}
/>
</TouchableWithoutFeedback>
</View>
</View>
</View>
</View>
);
}
}
class DiscoverPage extends React.Component {
static relay = {
queries: { viewer: () => Relay.QL`query { viewer }` },
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
events {
id
name
venue {
name
}
}
}
`,
},
};
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({ renderPlaceholderOnly: false });
});
}
componentWillReceiveProps(nextProps) {
if (!nextProps.relayLoading) {
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.setState({
dataSource: ds.cloneWithRows(nextProps.viewer.events),
});
}
}
_renderRow(event: Object, sectionID: number, rowID: number) {
return <DiscoverRow
onPress={() => Actions.event({ event })}
key={`comment-${rowID}`} event={event}
/>;
}
render() {
if (this.props.relayLoading || this.state.renderPlaceholderOnly) {
return <View><AHText>Relay loading</AHText></View>;
} else {
return (
<View style={styles.container}>
<AHNavBar title={'Discover'} leftTitle={""} rightImage={require('../img/filter.png')} />
<IncrementalListView
initialListSize={3}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`}
style={styles.separator} />}
/>
</View>
);
}
}
}
You should implement with a ListView instead of a ScrollView. ListView has performance optimizations that improve scrolling performance.
From the ListView section in the React Native Docs:
There are a few performance operations designed to make ListView
scroll smoothly while dynamically loading potentially very large (or
conceptually infinite) data sets