I have a React Native app running on Android with a Navigator component. I'm finding that when transitioning between scenes that the animation is only translating about 1/3 of the device width. I have seen that within the NavigatorSceneConfigs https://github.com/facebook/react-native/blob/v0.15.0/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js that the library is using the Dimensions of the device which is reporting 411.42857.... I'm running this on a Nexus 6 which is reporting a PixelRatio of 3.5. These numbers seem correct since multiplying these gives the devices resolution of 1440.
I have checked that the navigator and scene are filling the full device width. I would have expected it to translate the full width of the device. What have I missed configured or how have I missed understood what to expect from the Navigator animations?
class Root extends Component{
render() {
const routes = []
routes.push({
title: 'first'
})
routes.push({
title: 'second'
})
return (
<View style={{flex:1, backgroundColor: "transparent"}}>
<Navigator style={{flex:1, backgroundColor: 'red'}}
initialRouteStack={routes}
sceneStyle={{flex:1}}
renderScene={(route, navigator) =>
<View style={{flex: 1, backgroundColor:'transparent'}}>
<Text>{route.title}{Dimensions.get('window').width} {PixelRatio.get()}</Text>
</View>
}
/>
</View>
);
}
}
AppRegistry.registerComponent('Seed', () => Root);
This was a bug on ReactNative 0.14 and 0.15. I have justed test 0.16 and it is fixed.
https://github.com/facebook/react-native/issues/4221
Related
I am learning React Native. Following the guide, Getting Started of Facebook (
https://facebook.github.io/react-native/docs/props ). When I tried to make a new component containing an image sometimes works and sometimes not. I have read online that this is due to how they are load, React Native doesn't support dynamic loading of images.
class CuteImage extends Component {
render() {
return (
<View>
<Image source={{uri: 'https://cdn1.medicalnewstoday.com/content/images/articles/322/322868/golden-retriever-puppy.jpg'}} />
</View>
);
}
}
export default class ExampleComponent extends Component {
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', flex: "1", flexDirection: "column"}}>
<CuteImage />
</View>
);
}
}
I expected this to work fine because in the Image Component tutorial they did something similar and it had worked ( https://facebook.github.io/react-native/docs/image ) but it throws me the following error:
Failed to construct 'Image': Please use the 'new' operator
Why is this happening? How dynamic and static loading relates to this?
I think you forgot to add dimensions for your image.
The docs say here that you have to provide them when using network and data images.
I attached a screenshot of the result, it works now.
<View>
<Image style={{width: 380, height: 340} } source={{uri: 'https://cdn1.medicalnewstoday.com/content/images/articles/322/322868/golden-retriever-puppy.jpg'}} />
</View>
Cheers.
I use a FlatList component for images grid because it have a good performance:
<FlatList
data={photos}
columnWrapperStyle={styles.listColumn}
numColumns={4}
renderItem={photo => this.renderPhoto(photo)}
/>
For now renderPhoto func return a new FastImage component (i use it because it have a cool caching feature)
<FastImage
resizeMode={FastImage.resizeMode.cover}
source={{uri: photo.src}}
/>
In the end I have something like this:
But now I want to have a very familiar possibility. Tap on the image will start the animation after which the image will be stretched to the full width of the screen.
After that, the user can do the following:
Swipe left/right to see prev/next images from the FlatList
Zoom current image
Tap on image to show/hide control elements (footer and header)
Swipe up/down for closing carousel and return to the grid component
It might look something like this:
So, whats a problem?
All existing carousel solutions are a wrapper for an image collection. But I cant pass wrapper component inside FlatList.
I could not find a ready-made component for solving such a common problem.
There are some that I try to combine (Lightbox, Carousel). But this solution will greatly affect performance. (I need to load the entire collection of images from the FlatList into the carousel component) In addition, such solutions usually have problems with animation.
So I'm wondering if there really is no react-native solution for such a popular image view mechanics?
Perhaps it is worth making a native module on the swift/objc (FlatList of images with carousel modal)?
Actually is possible with the elements that you have.
First you have the carousel (react-native-looped-carousel):
const activeProps = {
resizeMode: 'contain',
flex: 1,
width: null
};
const { height, width } = Dimensions.get('window');
const renderCarousel = (album, currentPage) => (
<Carousel style={{ width, height }} currentPage={currentPage} autoplay={false}>
{album.map(image => (
<FastImage
style={{ flex: 1 }}
resizeMode={FastImage.resizeMode.contain}
source={{ uri: image.uri }}
key={image.uri}
/>))
}
</Carousel>
);
Then FastImage (react-native-fast-image) with the lightbox (react-native-lightbox):
LightImage = (props) => {
const currentPage = this.items.findIndex(x => x.uri === props.source.uri);
return (
<Lightbox
activeProps={activeProps}
renderContent={() => renderCarousel(this.items, currentPage)}
>
<FastImage {...props} />
</Lightbox>
);
}
Now you can use your renderItem with the component for the FastImage and Lightbox
<FlatList
data={this.items}
columnWrapperStyle={styles.listColumn}
numColumns={4}
renderItem={photo => this.LightImage(photo)}
/>
I've copied part of my code, so it won't work with just copy and paste. If you have any question feel free to ask!
There's only one problem with this implementation that if you rotate the device the layout breaks
I am trying to render a list of ~250 images in 3 columns using FlatList in RN0.43, and I change the width of the images in the onLayout function of the FlatList to fit the width of screen.
The initial performance is ok, but after some scrolling up/down, sometimes it takes a second or 2 until images are shown.
it is even worse if I change to screen orientation, it takes 2~3 seconds to get screen updated.
a few findings:
after screen rotation, it takes a second or 2 until FlatList.onLayout is called
after FlatList.onLayout and update of image width, each image (about half of the list, ~150 images; while only ~15 are shown) is rendered 2~4 times, while render() is only called once.
question:
how can I modify the code to improve the performance?
in the getItemLayout() of a multicolumn list, should the offset be something like (itemHeight + separatorHeight) * (index%numColumns)?
Thanks.
tested on: GalaxySII (4.1.2) and Android SDK emulator (7.1.1)
var data = [
require('./res/img/Search.png'),
require('./res/img/test - Copy.png'),
// ~250 items
...];
class app extends React.Component {
renderItem (info, width) {
console.log('renderItem', info.index);
if(width !== this.width) {
this.imageStyle = {width: width-MarginHorizontal , height: width-MarginHorizontal, resizeMode: 'contain'};
}
return (
<Image
source = {info.item}
key = {info.index}
style={this.imageStyle}/>
);
}
render() {
console.log('Test.render');
return (
<View style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: '#F5FCFF'
}}>
<GridList
numColumns={3}
columnWrapperStyle={{ alignItems: 'center', marginVertical: 5, justifyContent: 'space-around'}}
data={data}
renderItem={this.renderItem}
/>
</View>
);
}
}
class GridList extends Component {
onLayout(event) {
console.log('GridList.onLayout()');
let newColumnWidth = event.nativeEvent.layout.width/ this.numColumns;
this.layout = Object.assign({},event.nativeEvent.layout);
if( undefined === this.columnWidth || Math.abs(newColumnWidth - this.columnWidth) > WidthTolerance ) {
this.columnWidth = newColumnWidth;
if(this.isComponentMounted) {
this.setState({renderCount: this.state.renderCount+1});
} else {
this.state.renderCount +=1;
}
}
}
render() {
console.log('GridList.render()');
return (
<FlatList
{...(this.modifiedProps)}
renderItem={(info) => { return this.props.renderItem(info, this.columnWidth); }}>
{this.props.children}
</FlatList>
);
}
}
Disclaimer: I know that the question is old, but here is my response anyways.
My app has a hand full of lists with 500+ items. So, we got to a point where the app was crashing on popular not-bad phones. Then I've made this extensive research about performance on FlatLists.
The FlatList component was presented as a alternative for the old ScrollView. The problem is that ScrollViews render all your list at once so they perform visually better, but there is a trade off in memory consumption, that leads to app crashes.
So Flat Lists are a necessary evil. They essentially only render items that are visible, which is a huge gain on memory consumption, but a pain for visual performance, specially for heavy/complex items, that happens to be your case with those responsive images.
How to workaround?
There are a lot of strategies that you can implement to mitigate your problem.
Use cached and performatic images, such as react-native-fast-image. Every operation that you can remove or abbreviate for freeing the Javascript thread: do it (every image is a new Image(), so, if they are cached, you have your loaded hook called sooner)
Your list item component is a read-only component, which is supposed to be 'dumb'. Implement a shouldComponentUpdate() { return false } or a more solid update control method as needed. This is HUGE perf boost.
Remove console.logs anywhere near your list. They slow the Javascript thread really bad.
Build your app for production and test it. It becomes almost always twice or three times faster. Dev env is slow because of debugging.
Give this article a good read for more strategies.
Conclusion
FlatList IS a slow component. This is a known, open and well documented issue. Do what you can to improve it, and let's hope future releases may fix this.
Yes I was having the same problem - multiple images & videos in list in react native So I removed Flatlist instead of this I preferred to use ListView to render fast & to fix touchability issue on list item but Dont forget to set PureComponent to the list item
You're re-creating lots of styling objects for each row of the list individually. This puts a lot of traffic on the JS->Native bridge. Try using stylesheet instead of passing the styles inline.
I strongly recommend everybody to read the article in the link attached below to optimize your flatlist.
https://reactnative.dev/docs/optimizing-flatlist-configuration
Try to set unique key for each item using keyExtractor
For Example:
render() {
return (
<List>
<FlatList
...
keyExtractor={item => item.email}
/>
</List>
);
}
I am working on a react native app where I am trying to make some images act like a button so that when you press on them they print a statement in the console.
The images are displayed like this:
The code I have is:
class ImageList extends Component {
componentWillMount(){
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(this.props.images);
}
imageTouched(){
console.log('pressed');
}
renderRow(rowData){
const {uri} = rowData;
return(
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
)
}
render() {
return (
<View>
<ListView
contentContainerStyle={styles.list}
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</View>
);
}
}
var styles = StyleSheet.create({
imageSize: {
//newWidth is the width of the device divided by 4.
//This is so that four images will display in each row.
width: newWidth,
height: newWidth,
padding: 2
},
list: {
flexDirection: 'row',
flexWrap: 'wrap'
}
});
When I run it there are no errors but when I touch the images nothing happens. I have checked the console but nothing is printed.
How do I get each image to act as a button?
Like others have mentioned, the problem is that this is not bound in the renderRow()-method. I think the easiest way to fix this is to change renderRow() to be an arrow-function:
renderRow = (rowData) => {
const {uri} = rowData;
return (
<View style={{padding: 1, alignSelf: 'flex-start'}}>
<TouchableHighlight onPress={this.imageTouched}>
<Image style={styles.imageSize} source={{uri: uri}} />
</TouchableHighlight>
</View>
);
}
Arrow function always have this set to their containing scope when invoked, so now this.imageTouched will resolve.
Notice that you don't have to do anything with your imageTouched()-function or invocation, since it's not referencing this.
PS. This syntax depends on Public Class Fields, which is a Stage 2 proposal of the language standard at the time of writing (likely to be included, already in use in internal React-code). This feature is possible to use with a babel-plugin that is enabled by default in React Native projects.
PS2. Note that declaring the method with an arrow function instead of using an arrow function in the invocation will create one instance of the method per component instance, instead of one instance per render. This should really be fine performance-wise.
React components defined as ES6 classes do not autobind methods' this context to the component instance. In this particular case, the renderRow callback is not bound and the context refers to the global application context instead, so the reference to this.imageTouched is undefined.
A common pattern you see a lot is to bind the callbacks in your render method:
<ListView
renderRow={this.renderRow.bind(this)}
/>
This, however, has the effect of creating a new instance of the function on every render, which causes unnecessary work for the garbage collector.
Another alternative is to use (lexically scoped) arrow functions and call the methods explicitly:
<ListView
renderRow={(data) => this.renderRow(data);
/>
But this has the same unwanted effect of unnecessarily creating functions on every render.
A slightly more verbose, but more "correct" way is to bind the callbacks in the class constructor:
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
}
Try this
<TouchableHighlight onPress={() => this.imageTouched()}>
Make sure to bind renderRow
renderRow={this.renderRow.bind(this)}
I'm looking to animate a text field into view and a button out of view at the same time, so that it looks like the text field is replacing the button. (They are both equal size and take up the same area of the screen).
What's the best way to do this using React Native animation?
At this point, I am rendering the button if one of my state values is false, and the text field if it is true.
You can animate any style property in react-native using the Animated API.
If you are able to represent the changes in a sequence of style changes, the Animated API can do it. For instance animating the opacity from 1 to 0 and back to 1 will give a nice fade in fade out effect. The docs explain the Animations much more clearly
Also you can you selective rendering to mount or hide the component
<View style={{/*style props that need to be animated*/}}
{ boolShowText? <Text/> : <View/> }
</View>
The fading example as found in react-native docs
class FadeInView extends React.Component {
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // init opacity 0
};
}
componentDidMount() {
Animated.timing( // Uses easing functions
this.state.fadeAnim, // The value to drive
{toValue: 1}, // Configuration
).start(); // Don't forget start!
}
render() {
return (
<Animated.View // Special animatable View
style={{opacity: this.state.fadeAnim}}> // Binds
{this.props.children}
</Animated.View>
);
}
}