2015-07-16 12:44:56 +00:00
|
|
|
/**
|
2017-05-06 03:50:47 +00:00
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
2016-07-12 12:51:57 +00:00
|
|
|
*
|
2018-02-17 02:24:55 +00:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2016-07-12 12:51:57 +00:00
|
|
|
*
|
2018-05-11 20:32:37 +00:00
|
|
|
* @format
|
2018-08-09 15:32:04 +00:00
|
|
|
* @flow strict-local
|
2015-07-16 12:44:56 +00:00
|
|
|
*/
|
2018-05-11 20:32:37 +00:00
|
|
|
|
2015-07-16 12:44:56 +00:00
|
|
|
'use strict';
|
|
|
|
|
2016-04-09 03:36:40 +00:00
|
|
|
var React = require('react');
|
2017-07-07 21:24:25 +00:00
|
|
|
var createReactClass = require('create-react-class');
|
2016-04-09 03:36:40 +00:00
|
|
|
var ReactNative = require('react-native');
|
2018-05-11 20:32:37 +00:00
|
|
|
var {Image, ListView, TouchableHighlight, StyleSheet, Text, View} = ReactNative;
|
2015-07-16 12:44:56 +00:00
|
|
|
|
|
|
|
var THUMB_URLS = [
|
2015-12-30 22:49:13 +00:00
|
|
|
require('./Thumbnails/like.png'),
|
|
|
|
require('./Thumbnails/dislike.png'),
|
|
|
|
require('./Thumbnails/call.png'),
|
|
|
|
require('./Thumbnails/fist.png'),
|
|
|
|
require('./Thumbnails/bandaged.png'),
|
|
|
|
require('./Thumbnails/flowers.png'),
|
|
|
|
require('./Thumbnails/heart.png'),
|
|
|
|
require('./Thumbnails/liking.png'),
|
|
|
|
require('./Thumbnails/party.png'),
|
|
|
|
require('./Thumbnails/poke.png'),
|
|
|
|
require('./Thumbnails/superlike.png'),
|
|
|
|
require('./Thumbnails/victory.png'),
|
2015-07-16 12:44:56 +00:00
|
|
|
];
|
|
|
|
|
2017-07-07 21:24:25 +00:00
|
|
|
var ListViewGridLayoutExample = createReactClass({
|
|
|
|
displayName: 'ListViewGridLayoutExample',
|
2015-07-16 12:44:56 +00:00
|
|
|
|
|
|
|
statics: {
|
|
|
|
title: '<ListView> - Grid Layout',
|
2018-05-11 20:32:37 +00:00
|
|
|
description: 'Flexbox grid layout.',
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
|
|
|
|
return {
|
|
|
|
dataSource: ds.cloneWithRows(this._genRows({})),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
_pressData: ({}: {[key: number]: boolean}),
|
|
|
|
|
2018-02-08 18:26:45 +00:00
|
|
|
UNSAFE_componentWillMount: function() {
|
2015-07-16 12:44:56 +00:00
|
|
|
this._pressData = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
return (
|
|
|
|
// ListView wraps ScrollView and so takes on its properties.
|
|
|
|
// With that in mind you can use the ScrollView's contentContainerStyle prop to style the items.
|
|
|
|
<ListView
|
|
|
|
contentContainerStyle={styles.list}
|
|
|
|
dataSource={this.state.dataSource}
|
Fixed missing rows on UIExplorer <ListView> - Grid Layout example
Summary:
public
I was looking into the missing panels at the bottom of the <ListView> - Grid Layout example, and found that it was caused by several problems, some in the example and some in ListView itself.
The first problem seemed to be a bug in the `_getDistanceFromEnd()` method, which calculates whether the ListView needs to load more content based on the distance of the visible content from the bottom of the scrollview. This was previously using the function
Math.max(scrollProperties.contentLength, scrollProperties.visibleLength) - scrollProperties.visibleLength - scrollProperties.offset
to calculate the amount the user could scroll before they run out of content. This sort-of works in most cases because `scrollProperties.contentLength` is usually longer than `scrollProperties.visibleLength`, so this would generally evaluate to
scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset
which meant that it would be positive as long as there was content still to be displayed offscreen, and negative when you reached the end of the content. This logic breaks down if `contentLength` is less than `visibleLength`, however. For example, if you have 300pts of content loaded, and your scrollView is 500pts tall, and your scroll position is zero, this evaluates to
Math.max(300, 500) - 500 - 0 = 0
In other words, the algorithm is saying that you have zero pts of scroll content remaining before you need to reload. But actually, the bottom 200pts of the screen are empty, so you're really 200pts in debt, and need to load extra rows to fill that space. The correct algorithm is simply to get rid of the `Math.max` and just use
scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset
I originally thought that this was the cause of the gap, but it isn't, because ListView has `DEFAULT_SCROLL_RENDER_AHEAD = 1000`, which means that it tries to load at least 1000pts more content than is currently visible, to avoid gaps. This masked the bug, so in practice it wasn't causing an issue.
The next problem I found was that there is an implict assumption in ListView that the first page of content you load is sufficient to cover the screen, or rather, that the first _ second page is sufficient. The constants `DEFAULT_INITIAL_ROWS = 10` and `DEFAULT_PAGE_SIZE = 1`, mean that when the ListView first loads, the following happens:
1. It loads 10 rows of content.
2. It checks if `_getDistanceFromEnd() < DEFAULT_SCROLL_RENDER_AHEAD` (1000).
3. If it is, it loads another `DEFAULT_PAGE_SIZE` rows of content, then stops.
In the case of the ListView Grid Layout example, this meant that it first loaded 10 cells, then loaded another 1, for a total of 11. The problem was that going from 10 to 11 cells isn't sufficient to fill the visible scroll area, and it doesn't change the `contentSize` (since the cells wrap onto the same line), and since ListView doesn't try to load any more until the `contentSize` or `scrollOffset ` changes, it stops loading new rows at that point.
I tried fixing this by calling `_renderMoreRowsIfNeeded()` after `_pageInNewRows()` so that it will continue to fetch new rows until the `_getDistanceFromEnd()` is less than the threshold, rather than stopping after the first page and waiting until the `contentSize` or `scrollOffset` change, but although this solves the problem for the Grid Layout example, it leads to over-fetching in the more common case of a standard row-based ListView.
In the end, I just increased the `pageSize` to 3 for the Grid Layout example, which makes more sense anyway since loading a page that is not a multiple of the number of cells per row confuses the `_renderMoreRowsIfNeeded` algorithm, and leads to gaps at the bottom of the view.
This solved the problem, however there was still a "pop-in" effect, where the additional rows were paged in after the ListView appeared. This was simply a misconfiguration in the example itself: The default of 10 rows was insufficient to fill the screen, so I changed the `initialListSize` prop to `20`.
Reviewed By: javache
Differential Revision: D2911690
fb-gh-sync-id: 8d6bd78843335fb091e7e24f7c2e6a416b0321d3
shipit-source-id: 8d6bd78843335fb091e7e24f7c2e6a416b0321d3
2016-02-10 16:36:15 +00:00
|
|
|
initialListSize={21}
|
|
|
|
pageSize={3} // should be a multiple of the no. of visible cells per row
|
|
|
|
scrollRenderAheadDistance={500}
|
2015-07-16 12:44:56 +00:00
|
|
|
renderRow={this._renderRow}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
_renderRow: function(rowData: string, sectionID: number, rowID: number) {
|
|
|
|
var rowHash = Math.abs(hashCode(rowData));
|
2015-12-30 22:49:13 +00:00
|
|
|
var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length];
|
2015-07-16 12:44:56 +00:00
|
|
|
return (
|
2018-05-11 20:32:37 +00:00
|
|
|
<TouchableHighlight
|
|
|
|
onPress={() => this._pressRow(rowID)}
|
|
|
|
underlayColor="transparent">
|
2015-07-16 12:44:56 +00:00
|
|
|
<View>
|
|
|
|
<View style={styles.row}>
|
|
|
|
<Image style={styles.thumb} source={imgSource} />
|
2018-05-11 20:32:37 +00:00
|
|
|
<Text style={styles.text}>{rowData}</Text>
|
2015-07-16 12:44:56 +00:00
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
</TouchableHighlight>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
_genRows: function(pressData: {[key: number]: boolean}): Array<string> {
|
|
|
|
var dataBlob = [];
|
|
|
|
for (var ii = 0; ii < 100; ii++) {
|
|
|
|
var pressedText = pressData[ii] ? ' (X)' : '';
|
|
|
|
dataBlob.push('Cell ' + ii + pressedText);
|
|
|
|
}
|
|
|
|
return dataBlob;
|
|
|
|
},
|
|
|
|
|
|
|
|
_pressRow: function(rowID: number) {
|
|
|
|
this._pressData[rowID] = !this._pressData[rowID];
|
2018-05-11 20:32:37 +00:00
|
|
|
this.setState({
|
|
|
|
dataSource: this.state.dataSource.cloneWithRows(
|
|
|
|
this._genRows(this._pressData),
|
|
|
|
),
|
|
|
|
});
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
/* eslint no-bitwise: 0 */
|
|
|
|
var hashCode = function(str) {
|
|
|
|
var hash = 15;
|
|
|
|
for (var ii = str.length - 1; ii >= 0; ii--) {
|
2018-05-11 20:32:37 +00:00
|
|
|
hash = (hash << 5) - hash + str.charCodeAt(ii);
|
2015-07-16 12:44:56 +00:00
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
};
|
|
|
|
|
|
|
|
var styles = StyleSheet.create({
|
|
|
|
list: {
|
|
|
|
justifyContent: 'space-around',
|
|
|
|
flexDirection: 'row',
|
2016-06-22 15:36:32 +00:00
|
|
|
flexWrap: 'wrap',
|
2018-05-11 20:32:37 +00:00
|
|
|
alignItems: 'flex-start',
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
row: {
|
|
|
|
justifyContent: 'center',
|
|
|
|
padding: 5,
|
|
|
|
margin: 3,
|
|
|
|
width: 100,
|
|
|
|
height: 100,
|
|
|
|
backgroundColor: '#F6F6F6',
|
|
|
|
alignItems: 'center',
|
|
|
|
borderWidth: 1,
|
|
|
|
borderRadius: 5,
|
2018-05-11 20:32:37 +00:00
|
|
|
borderColor: '#CCC',
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
thumb: {
|
|
|
|
width: 64,
|
2018-05-11 20:32:37 +00:00
|
|
|
height: 64,
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
text: {
|
|
|
|
flex: 1,
|
|
|
|
marginTop: 5,
|
2018-05-11 20:32:37 +00:00
|
|
|
fontWeight: 'bold',
|
2015-07-16 12:44:56 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = ListViewGridLayoutExample;
|