问题
As you can see in the picture above, there is a vertical list of two columns. I have developed two flatLists because I can not style photos with different height on one line with the flatList column option.
I want two flatLists to have the same scroll position.
I wrapped two FlatList component with to synchronize scrolling. But there is a problem here. The scrollView ignores the infinity scroll and calls the pagination objects all at once. There is a performance problem.
How can I solve this problem?
here is my component code
import React from 'react';
import { View, Platfrom, Text, StyleSheet, AsyncStorage, TouchableOpacity, Image, FlatList, Button, Dimensions, ScrollView, ListView } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import { NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import moment from 'moment';
import * as settings from '../../config/settings';
import Menu from '../menu';
import headerStyles from '../../styles/common/header';
import textStyles from '../../styles/common/text';
import containerStyles from '../../styles/common/container';
import imageStyles from '../../styles/common/image';
import boxStyles from '../../styles/common/box';
import objectStyles from '../../styles/common/object';
import commonStyles from '../../styles/common/common';
import colorStyles from '../../styles/common/color';
const window = Dimensions.get('window');
const styles = StyleSheet.create({
listView: {
paddingTop: 20,
backgroundColor: '#FFFFFF',
},
fullScreen: {
flex: 1,
marginBottom: 50,
},
floatView: {
position: 'absolute',
width: '90%',
marginLeft: '5%',
height: 100,
bottom: -50,
},
});
const phoneWidth = Dimensions.get('window').width
class PurchaseListScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state
return {
title: '중고 명품 구매',
headerStyle: headerStyles.header,
headerTitleStyle: headerStyles.headerTitle,
headerRight: <Icon name="bars" size={30} color="#333" onPress={() => { params.showModal() }} style={{ padding: 10 }} />,
}
}
constructor(props) {
super(props);
this.state = {
user_token: '',
refreshing: false,
isModalVisible: false,
leftColumneData: [],
rightColumneData: [],
leftNextKey: '',
rightNextKey: '',
};
this.hideModal = this.hideModal.bind(this);
}
componentDidMount() {
this.props.navigation.setParams({ showModal: this.showModal });
this.purchaseListService();
}
showModal = () => this.setState({ isModalVisible: true })
hideModal = () => this.setState({ isModalVisible: false })
navigate1 = (item) => {
console.log('click navigate1');
const navigate1 = NavigationActions.navigate({
routeName: "home",
});
this.props.navigation.dispatch(navigate1);
};
purchaseListService = async () => {
if (this.props.isLoggedIn) {
let user_info = await AsyncStorage.getItem('user_info');
get_user_token = JSON.parse(user_info).key;
this.setState({ user_token: get_user_token });
const api_uri1 = settings.base_uri + 'purchase-product/odd/'
const request1 = {
method: 'GET',
headers: {
'Authorization': 'Token ' + get_user_token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const api_uri2 = settings.base_uri + 'purchase-product/even/'
const request2 = {
method: 'GET',
headers: {
'Authorization': 'Token ' + get_user_token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
fetch(api_uri1, request1)
.then(res => res.json())
.then(res => {
this.setState({
leftColumneData: [...res.results],
leftNextKey: res.next
})
})
.catch((error) => {
console.error(error);
})
fetch(api_uri2, request2)
.then(res => res.json())
.then(res => {
this.setState({
rightColumneData: [...res.results],
rightNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
} else {
const api_uri1 = settings.base_uri + 'purchase-product/odd/'
const request1 = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const api_uri2 = settings.base_uri + 'purchase-product/even/'
const request2 = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
fetch(api_uri1, request1)
.then(res => res.json())
.then(res => {
this.setState({
leftColumneData: [...res.results],
leftNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
fetch(api_uri2, request2)
.then(res => res.json())
.then(res => {
this.setState({
rightColumneData: [...res.results],
rightNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
}
}
onEndReachedService = () => {
console.log('run onEndReachedService');
if (this.props.isLoggedIn && this.state.leftNextKey) {
const api_uri1 = this.state.leftNextKey
const request1 = {
method: 'GET',
headers: {
'Authorization': 'Token ' + this.state.user_token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const api_uri2 = this.state.rightNextKey
const request2 = {
method: 'GET',
headers: {
'Authorization': 'Token ' + this.state.user_token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
fetch(api_uri1, request1)
.then(res => res.json())
.then(res => {
this.setState({
leftColumneData: [...this.state.leftColumneData, ...res.results],
leftNextKey: res.next
})
})
.catch((error) => {
console.error(error);
})
fetch(api_uri2, request2)
.then(res => res.json())
.then(res => {
this.setState({
rightColumneData: [...this.state.rightColumneData, ...res.results],
rightNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
} else if (!this.props.isLoggedIn && this.state.leftNextKey){
const api_uri1 = this.state.leftNextKey
const request1 = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const api_uri2 = this.state.rightNextKey
const request2 = {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
fetch(api_uri1, request1)
.then(res => res.json())
.then(res => {
console.log('update res1', res);
this.setState({
leftColumneData: [...this.state.leftColumneData, ...res.results],
leftNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
fetch(api_uri2, request2)
.then(res => res.json())
.then(res => {
console.log('update res2', res);
this.setState({
rightColumneData: [...this.state.rightColumneData, ...res.results],
rightNextKey: res.next,
})
})
.catch((error) => {
console.error(error);
})
}
}
likeService = async (pk, column) => {
let user_info = await AsyncStorage.getItem('user_info');
user_token = JSON.parse(user_info).key;
const api_uri = settings.base_uri + 'purchase-product/' + pk + '/like/'
var request = {
method: 'POST',
headers: {
'Authorization': 'Token ' + user_token,
'Accept': 'application/json',
'Content-Type': 'application/json',
}
};
fetch(api_uri, request)
.then(res => {
if(column=='left'){
var items = this.state.leftColumneData
for (i=0; i<items.length; i++) {
let item = items[i]
if (item.id==pk) {
item.is_liked = !item.is_liked
items[i] = item;
this.setState({ leftColumneData: items })
}
}
} else {
var items = this.state.rightColumneData
for (i = 0; i < items.length; i++) {
let item = items[i]
if (item.id == pk) {
item.is_liked = !item.is_liked
items[i] = item;
this.setState({ rightColumneData: items })
}
}
}
})
.catch((error) => {
console.error(error);
});
}
renderLabel = (text) => {
if (text) {
return (
<View style={{ backgroundColor: 'red', paddingVertical: 2, marginLeft: 3, marginTop: 5, marginBottom: 8, paddingHorizontal: 4 }}>
<Text style={{ color: 'white', fontSize: 9, fontWeight: '400' }}>{text}</Text>
</View>
)
} else {
return (
<View style={{ paddingVertical: 2, marginTop: 5, marginBottom: 8, }}>
</View>
)
}
}
renderList = (item, column) => {
return(
<View>
<View style={{
flex: 1,
borderColor: 'rgb(136, 136, 136)',
borderWidth: 1,
marginLeft: 15,
marginTop: 15,
backgroundColor: 'white',
}}>
<TouchableOpacity onPress={()=>{}}>
{item.is_vertical?
<Image
source={{ uri: item.first_thumbnail }}
style={{ flex:1, height: 210 }}/>
:
<Image
source={{ uri: item.first_thumbnail }}
style={{ flex: 1, height: 130 }} />
}
</TouchableOpacity>
<View style={{ flex: 1 }}>
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity style={{ flex: 4 }}>
<Text style={{
fontSize: 14,
fontWeight: '600',
color: 'rgb(35, 31, 32)',
marginTop: 10,
marginLeft: 8
}}>{item.name}</Text>
<Text style={{
fontSize: 12,
fontWeight: '500',
color: 'rgb(35, 31, 32)',
marginLeft: 8,
marginTop: 8
}}>{item.partner.biz_name}</Text>
<View style={{ flexDirection: 'row', marginLeft: 6 }}>
{this.renderLabel(item.product_type_str)}
{this.renderLabel(item.city.name)}
</View>
</TouchableOpacity>
<View
style={{ flex: 1, marginTop: 10, marginRight: 2, alignItems: 'center' }}>
{this.props.isLoggedIn ?
<View>
{item.is_liked ?
<Icon
name="heart" size={17} color="red"
onPress={() => {
this.likeService(item.id, column);
}} />
: <Icon name="heart-o" size={17} color="red"
onPress={() => {
this.likeService(item.id, column);
}} />
}
</View> :
<View>
<Icon name="heart-o" size={17} color="red"
onPress={() => {
console.log('press heart', column);
}} />
</View>}
</View>
</View>
<View style={[ colorStyles.greenBackground,
{
flex: 1,
borderTopWidth: 1,
borderColor: 'rgb(136, 136, 136)',
padding: 5,
paddingRight: 10,
}]}>
<Text style={{
textAlign: 'right',
fontSize: 14,
fontWeight: '500',
color: 'rgb(35, 31, 32)',
}}>
{(item.price).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}원 {item.id}
</Text>
</View>
</View>
</View>
</View>
)
}
render() {
return (
<View style={{ flex: 1 }}>
<Menu hideModal={this.hideModal.bind(this)} isVisible={this.state.isModalVisible} navigation={this.props.navigation} />
<View style={{ flexDirection: 'row' }}>
<FlatList
style={{ flex: 1 }}
showsVerticalScrollIndicator={false}
initialNumToRender={20}
onEndReachedThreshold={1}
onEndReached={this.onEndReachedService}
refreshing={this.state.refreshing}
onRefresh={this.purchaseListService.bind(this)}
data={this.state.leftColumneData}
renderItem={({ item }) => this.renderList(item, column='left')}
keyExtractor={(item) => item.id}
/>
<FlatList
style={{ flex: 1, paddingRight: 15 }}
showsVerticalScrollIndicator={false}
refreshing={this.state.refreshing}
onRefresh={this.purchaseListService.bind(this)}
data={this.state.rightColumneData}
renderItem={({ item }) => this.renderList(item, column='right')}
keyExtractor={(item) => item.id}
/>
</View>
</View>
)
}
}
const mapStateToProps = state => ({
isLoggedIn: state.loginReducer.isLoggedIn
})
const PurchaseList = connect(mapStateToProps, null)(PurchaseListScreen);
export default PurchaseList;
来源:https://stackoverflow.com/questions/48057527/how-can-i-sync-two-flatlist-scroll-position-in-react-native