I have a little problem with the FlatList Component in react native. In the FlatList are items with different height. Here is a little example of what i have:
example FlatL
The first problem is that all items are positioned at the top. I want to position all items at the bottom.
The second problem is that the height of the FlatList is always the height of the biggest item. So you can also scroll to another item in the white area of a small item...
here my code:
import React from "react";
import {
Text,
View,
Dimensions,
StyleSheet,
ListView,
TouchableOpacity,
Animated,
Image,
FlatList
} from "react-native";
import glamorous, { ThemeProvider } from "glamorous-native";
import theme from "../theme";
const { height, width } = Dimensions.get("window");
const cards = [
{
id: 1,
color: "red",
height: 400
},
{
id: 2,
color: "blue",
height: 300
},
{
id: 3,
color: "yellow",
height: 200
}
];
class Test extends React.Component {
render() {
return (
<View style={{ bottom: 0 }}>
<FlatList
ref={elm => (this.flatList = elm)}
showsHorizontalScrollIndicator={false}
data={cards}
pagingEnabled={true}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View
style={{
height: item.height,
width: width,
backgroundColor: item.color
}}
/>
)}
/>
</View>
);
}
}
export default Test;
Has anyone a solution?
You can change the position of each item by setting the contentContainerStyle property of the FlatList itself. But the height of the FlatList will always have to be the height of the largest component inside it.
I solved this with onViewableItemsChanged prop
viewabilityConfig={{itemVisiblePercentThreshold: 50}}
onViewableItemsChanged={this.onViewableItemsChanged}
onViewableItemsChanged = ({ viewableItems }) => {
let index = viewableItems[0].index;
this.setState({ indexOfImages: index, heightOfImages: SCREEN_WIDTH * (viewableItems[0].item.height / viewableItems[0].item.width) });
}
We have width and height of the items in its data so I'm taking width and height of the visible item on the screen and do some calculations and I'm using this.state.heightOfImages in flatlist height like this
height: this.state.heightOfImages == undefined ? SCREEN_WIDTH * (images[0].height / images[0].width) : this.state.heightOfImages
You can just wrap the content of each card in another <View> element with styles={{ minHeight: 200, maxHeight: 300 }} and it will work fine!
Related
I'm trying to design an application screen (I used React Navigation) where several images are displayed vertically inside a FlatList. Each image takes up around 90% of the screen, is centered and has some border-radius, to see the other images, the user has to scroll down.
All these things I was able to do myself, the only problem is, when I scroll down the first image, there is a huge empty gap at the top before showing image 2, and when I scroll down again to see image 3, the bottom bit of image 2 is still showing in the upper part of the screen, separated by another huge gap and the bottom of image 3 is cut out of the screen.
What I've been trying to do is to show exactly one full image at a time, and I don't know whether there's something wrong with the scrolling itself, or the styling I applied to the components.
Here's the code:
import {View, FlatList, Image, Dimensions} from 'react-native';
import {pictures} from './pictures.js';
const ImageScreen = () => {
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('screen').height;
function renderImages(pictures){
return(
<View style={{ width: deviceWidth, height: deviceHeight, alignItems:'center'}}>
<Image source={{uri: pictures.item.imageId}}
style={{height:'90%',width:'90%', borderRadius:22 }}/>
</View>
);}
return(
<FlatList data={pictures} keyExtractor={(item)=> item.id} renderItem={renderImages}
pagingEnabled={true} />
);
};
export default SightOverview;
please find below code to display list:
import * as React from 'react';
import { View, FlatList, Image, Dimensions } from 'react-native';
const App = () => {
const pictures = [{
imageId: 'https://picsum.photos/id/1/200/300'
}]
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('screen').height;
function renderImages({ item }) {
console.log(item)
return (
<View style={{ flex: 1, width: deviceWidth, height: deviceHeight, alignItems: 'center' }}>
<Image source={{ uri: item.imageId }}
style={{ width: deviceWidth * 0.8, height: deviceHeight * 0.5, borderRadius: 22 }} />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList data={pictures} keyExtractor={(item) => item.id} renderItem={renderImages}
pagingEnabled={true} />
</View>
);
};
export default App;
I have a React-Native application where I am using FlatList to display a list of items obtained from the server. The list has 2 columns and I need my list items to be the same height. I put a border around the code rendering my list items but the list items are not the same height. I have tried using flexbox settings to make the view fill the container, but everything I try makes no difference.
I have created a simplified version of my app to illustrate the issue:
See that the red bordered areas are NOT the same height. I need to get these to be the same height.
The grey border is added in the view wrapping the component responsible for a list item and the red border is the root view of the component responsible for a list item. See the code below for clarity.
I can not use the grey border in my application because my application shows empty boxes whilst the component responsible for a list item is getting additional information from the server before it renders itself
Furthermore I can not used fixed sizes for heights.
Application Project structure and code
My code is split up in a manner where the files ending in "container.js" get the data from the server and pass it to its matching rendering component. For example, "MainListContainer" would be getting the list from the server and then pass the list data to "MainList", and "ListItemContainer" would get additional information about the single list item from the server and pass it to "ListItem" to render the actual item. I have kept this model in my simplified application so its as close to my real application as possible.
index.js
import {AppRegistry} from 'react-native';
import MainListContainer from './app/components/MainListContainer';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => MainListContainer);
MainListContainer.js
import React from 'react';
import MainList from './MainList';
const data = [
{id: '1', title: 'Item 1', subtitle: 'A', description: 'This is the first item.'},
{id: '2', title: 'Item 2', subtitle: 'B', description: 'The Big Brown Fox Jumped over the lazy dogs. The Big Brown Fox Jumped over the lazy dogs.',},
];
const MainListContainer = () => {
return ( <MainList items={data} /> );
};
export default MainListContainer;
MainList.js
import React from 'react';
import {StyleSheet, FlatList, View} from 'react-native';
import ListItemContainer from './ListItemContainer';
export default class MainList extends React.Component {
constructor(props) {
super(props);
this.state = { numColumns: 2};
this.renderItem = this.renderItem.bind(this);
}
renderItem({item, index}) {
return (
<View style={styles.flatListItemContainer}> <!-- THIS IS WHERE THE GREY BORDER IS ADDED -->
<ListItemContainer key={index} item={item} />
</View>
);
}
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
}
};
const styles = StyleSheet.create({
flatListItemContainer: {
flex: 1,
margin: 10,
borderColor: '#ccc',
borderWidth: 1,
},
});
ListItemContainer.js
import React from 'react';
import ListItem from './ListItem';
const ListItemContainer = (props) => {
const { item } = props;
return (
<ListItem item={item} />
);
};
export default ListItemContainer;
ListItem.js
import React from 'react';
import {TouchableHighlight, View, StyleSheet, Image, Text} from 'react-native';
const ListItem = (props) => {
const { item } = props;
return (
<TouchableHighlight
underlayColor="white"
>
<View style={styles.containerView}> <!-- THIS IS WHERE THE RED BORDER IS ADDED -->
<View style={styles.top_row}>
<Image style={styles.image} source={require('../images/placeholder.png')} />
<View style={styles.title_texts}>
<Text style={{fontWeight:'bold'}}>{item.title}</Text>
<Text style={{color: 'rgb(115, 115, 115)'}}>{item.subtitle}</Text>
</View>
</View>
<Text>{item.description}</Text>
</View>
</TouchableHighlight>
);
};
export default ListItem;
const styles = StyleSheet.create({
containerView: {
padding: 14,
borderColor: 'red',
borderWidth: 1,
},
top_row: {
flex: 1,
flexDirection: 'row',
marginBottom: 10,
},
title_texts: {
flex: 1,
flexDirection: 'column',
},
image: {
alignSelf: 'flex-end',
resizeMode: 'cover',
height: 40,
width: 40,
marginRight: 20
},
});
What I have tried
ListItem.js : move the style onto the "TouchableHighlight" view
ListItem.js : add a view wrapping "TouchableHighlight" view and adding style there
ListItem.js : added "alignItems:'stretch' on the "TouchableHighlight, added it to the "containerView" style, tried it on the description field too
same as "alignItems" but used "alignedSelf" instead
same as "alignItems" but used "alignedContent" instead
tried using "flexGrow" on different views (container, description)
You can measure the height of every element in the list and when you determine the maximum height, you can use that height for every element in the list.
const Parent = ({ ...props }) => {
const [maxHeight, setMaxHeight] = useState<number>(0);
const computeMaxHeight = (h: number) => {
if (h > maxHeight) setMaxHeight(h);
}
return (
<FlatList
data={props.data}
renderItem={({ item }) => (
<RenderItem
item={item}
computeHeight={(h) => computeMaxHeight(h)}
height={maxHeight}
/>
)}
....
/>
)
}
The Items:
const RenderItem = ({...props }) => {
return (
<View
style={{ height: props.height }}
onLayout={(event) => props.computeHeight(event.nativeEvent.layout.height)}
>
<Stuffs />
</View>
)
}
This is a very non-performant way of achieving this. I would avoid this if I have a long list or any list of more than a few items. You however can put certain checks in place to limit rerendering etc. Or alternatively if it is only text that will affect the height, then you can only measure the height of the element with the most text and use that element's height for the rest.
Instead of set fixed width height, you can use flex box to achieve it. I just solved the issue by removing alignSelf at the FlatList and add alignItems center on it.
Wrap the flatList in flex box with align item center, you can add the code in your MainList.js file, the first <View>, i.e:
render() {
const {items} = this.props;
const {numColumns} = this.state;
return (
<View style={{flex: 1, alignItems: 'center'>
<FlatList
data={items}
renderItem={this.renderItem}
numColumns={numColumns}
key={numColumns}
keyExtractor={(item) => item.id}
/>
</View>
);
If still not reflected, you may try to add flex:1, alignItems center in FlatList style props.
You are missing a very basic concept of giving fixed height to the flatlist items, in your ListItem.js, try to set height:200 in containerView. Let me know if that works for you
I'm trying to make a list of items in FlatList overlap over each other like a stack of cards, but using a negative margin the item gets cut off, using "left: -20" does as well.
The image component is rather simple with round border:
export default class ProfilePicture extends React.Component {
render () {
let size = this.props.size || 50
return (
<Image
source={{ uri: this.props.picture }}
style={{
backgroundColor: 'rgba(12, 94, 20, 0.5);',
width: size,
height: size,
borderRadius: size / 2
}}
/>
)
}
}
And in the list is where I try to accomplish the overlap:
export default class RidersListCompact extends Component {
state = {
users: []
}
...
renderItem = ({ item: user, index }) => {
return <View style={styles.itemContainer}>
<ProfilePicture
picture={user.picture}
size={Layout.window.hp(6)}
/>
</View>
}
render () {
return (
<FlatList
renderItem={this.renderItem}
data={this.state.users}
keyExtractor={(user) => 'user_' + user.id}
horizontal
inverted
style={{ ...styles.container, ...this.props.style }}
/>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row-reverse'
},
itemContainer: {
marginRight: -Layout.window.hp(2),
width: Layout.window.hp(6),
height: Layout.window.hp(6),
backgroundColor: 'rgba(0,0,0,0);'
}
})
I tried setting different zIndex on each item but haven't had much luck, is there a way to overlap images/components in FlatList?
Cheers!
Make use of Flex. seperate Items by putting then in flex direact row wise. use Props from flex. Flex has following props available
alignContent
alignItems
alignSelf
aspectRatio
borderBottomWidth
borderEndWidth
borderLeftWidth
borderRightWidth
borderStartWidth
borderTopWidth
borderWidth
bottom
direction
display
end
flex
flexBasis
flexDirection
flexGrow
flexShrink
flexWrap
height
justifyContent
left
margin
marginBottom
marginEnd
marginHorizontal
marginLeft
marginRight
marginStart
marginTop
marginVertical
maxHeight
maxWidth
minHeight
minWidth
overflow
padding
paddingBottom
paddingEnd
paddingHorizontal
paddingLeft
paddingRight
paddingStart
paddingTop
paddingVertical
position
right
start
top
width
zIndex
If you want to overlap images you should use position style in your styles. You need to set position to absolute and set left, right, top, bottom values.
More information
I am seeking a way to scroll a viewport over a table like this, except that every cell is exactly the same size:
I am currently using FlatList's numColumns parameter to make a table and scroll the viewport over that table.
Here is a Snack example - RegularGridExample:
import React from 'react';
import { FlatList, Text, View } from 'react-native';
const numRows = 10,
numColumns = 10,
width = 100,
height = 100,
cells = [...Array(numRows * numColumns)].map((_, cellIndex) => {
const rowIndex = Math.floor(cellIndex / numRows),
colIndex = cellIndex % numColumns;
return {
key: `${colIndex},${rowIndex}`,
rowIndex,
colIndex,
styles: {
width,
height,
backgroundColor: 'green',
borderColor: 'black',
borderWidth: 1,
},
};
});
export default class RegularGridExample extends React.Component {
render() {
return (
<FlatList
data={cells}
renderItem={this.renderItem}
numColumns={numColumns}
horizontal={false}
columnWrapperStyle={{
borderColor: 'black',
width: numColumns * width,
}}
/>
);
}
renderItem = ({ item: { styles, rowIndex, colIndex } }) => {
return (
<View style={styles}>
<Text>r{rowIndex}</Text>
<Text>c{colIndex}</Text>
</View>
);
};
}
This example will correctly scroll to reveal the rows below the viewport, but it will not scroll to reveal the columns beyond the viewport. How can I enable scrolling the viewport to reveal a FlatList's columns?
Update 1
I do not think this can be easily solved with nested FlatLists, which is the first thing I tried before using the numColumns approach above. The use case here is shifting the viewport over a grid that's larger than the viewport, not just scrolling one row within the viewport.
Update 2
I'm seeking a virtualized solution. While the wireframe above uses text, the use case I'm actually interested in is browsing a tile server navigating over portions of a large 50MB+ image. It will be too slow to load all of them into a scroll view.
Unrelated Stack Overflow Posts
React Native ScrollView/FlatList not scrolling - this is about adding flex to the viewport to enable scrolling along the major axis of the FlatList, which is already working in the example above. My concern is scrolling along the crossAxis.
React native flatlist not scrolling - it is unclear what the expected and actual behavior is here
How can I sync two flatList scroll position in react native - here, the poster is seeking to simulate masonry layout; I'm not doing anything so fancy
This can't be done using the FlatList method, since numColumns is used which explicitly sets horizontal={false}, hence disabling the scrolling horizontal direction.
Here's a workaround by using nested ScrollViews
export default class RegularGridExample extends React.Component {
render() {
const generatedArray = [1,2,3,4,5,6]
return (
<ScrollView horizontal>
<ScrollView >
{generatedArray.map((data, index) => {
return <View style={{flexDirection: 'row'}} >
{generatedArray.map((data, index) => {
return <View style={{height: 100, width: 100, backgroundColor: 'red', borderWidth: 1, borderColor: 'black'}} />
})}
</View>
})}
</ScrollView>
</ScrollView>
);
}
}
I'm learning flex layout for react native. This is my code:
import React, { Component } from 'react';
import {
Text,
View,
ScrollView
} from 'react-native';
const style = {
container: {
flex:1,
flexDirection:'column',
flexWrap:'nowrap',
alignItems:'stretch',
},
title: {
flex:1,
backgroundColor:'blue',
},
terms: {
flex:8,
flexDirection:'column',
backgroundColor:'red',
},
termsText: {
padding:15,
flex:1,
},
check: {
flex:1,
backgroundColor:'yellow',
}
};
export default class Home extends Component {
constructor(props)
{
super(props);
}
render() {
return (
<View style={style.container}>
<Text style={style.title}>Terms and Conditions</Text>
<ScrollView style={style.terms}>
<Text style={style.termsText}>You must comply, or die...</Text>
</ScrollView>
<Text style={style.check}>Check mark here (tap to agree to terms)</Text>
</View>
);
}
}
And the result is this:
This is not what I expected. I was expecting the red area to take up 80% of the vertical height because style.terms.flex == 8. If I changed the contents of my render to this:
render() {
return (
<View style={style.container}>
<Text style={style.title}>Terms and Conditions</Text>
<Text style={style.terms}>You must comply, or die...</Text>
<Text style={style.check}>Check mark here (tap to agree to terms)</Text>
</View>
);
}
I am then able to get the red area to become 80% vertical height. Why does ScrollView not respect style.terms.flex?
"Keep in mind that ScrollViews must have a bounded height in order to work, since they contain unbounded-height children into a bounded container (via a scroll interaction)." From the react native docs. What you can do is use the Dimensions API of react native and give the ScrollView 80% of device height instead.
More on ScrollView
Dimensions
Use Dimensions Api for ScrollView
const { width, height } = Dimensions.get('window');
<ScrollView style={{ height: height * 0.8 }}>
<Text style={style.termsText}>You must comply, or die...</Text>
</ScrollView>
height * 0.8 get the 80 percentage of height with respect to the screen size