Add renderSeparator support to ListView

This commit is contained in:
Changgeng Li 2015-05-26 15:16:42 -07:00
parent b839ab32be
commit 84783dbac4
5 changed files with 112 additions and 7 deletions

View File

@ -34,7 +34,10 @@ var MovieCell = React.createClass({
var criticsScore = this.props.movie.ratings.critics_score; var criticsScore = this.props.movie.ratings.critics_score;
return ( return (
<View> <View>
<TouchableHighlight onPress={this.props.onSelect}> <TouchableHighlight
onPress={this.props.onSelect}
onShowUnderlay={this.props.onHighlight}
onHideUnderlay={this.props.onUnhighlight}>
<View style={styles.row}> <View style={styles.row}>
<Image <Image
source={getImageSource(this.props.movie, 'det')} source={getImageSource(this.props.movie, 'det')}
@ -54,7 +57,6 @@ var MovieCell = React.createClass({
</View> </View>
</View> </View>
</TouchableHighlight> </TouchableHighlight>
<View style={styles.cellBorder} />
</View> </View>
); );
} }

View File

@ -16,6 +16,7 @@
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14312D241AC3654D00CDC950 /* libRCTLinking.a */; }; 140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14312D241AC3654D00CDC950 /* libRCTLinking.a */; };
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14A2D4411AC3E41A00CC738A /* libReact.a */; }; 14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14A2D4411AC3E41A00CC738A /* libReact.a */; };
58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C5725B1AA6236500CDF9C8 /* libRCTText.a */; }; 58C5726B1AA6239E00CDF9C8 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C5725B1AA6236500CDF9C8 /* libRCTText.a */; };
67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -54,6 +55,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676; remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText; remoteInfo = RCTText;
}; };
67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
remoteInfo = RCTWebSocket;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -69,6 +77,7 @@
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; }; 14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = "<group>"; }; 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = "<group>"; };
587650F61A9EB120008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; }; 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; };
67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -76,6 +85,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
67C95F201B0E64A30040BCE2 /* libRCTWebSocket.a in Frameworks */,
14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */, 14A2D4421AC3E43800CC738A /* libReact.a in Frameworks */,
140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */, 140D9B661AC36C42004F25EE /* libRCTLinking.a in Frameworks */,
1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */, 1341801E1AA91750003F314A /* libRCTNetwork.a in Frameworks */,
@ -135,6 +145,7 @@
58C571FC1AA6124500CDF9C8 /* Libraries */ = { 58C571FC1AA6124500CDF9C8 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */,
14A2D43C1AC3E41A00CC738A /* React.xcodeproj */, 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */,
14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */, 14312D1E1AC3654D00CDC950 /* RCTLinking.xcodeproj */,
134180151AA91740003F314A /* RCTNetwork.xcodeproj */, 134180151AA91740003F314A /* RCTNetwork.xcodeproj */,
@ -152,6 +163,14 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
67C95F161B0E647A0040BCE2 /* Products */ = {
isa = PBXGroup;
children = (
67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */,
);
name = Products;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = { 83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -228,6 +247,10 @@
ProductGroup = 58C572571AA6236500CDF9C8 /* Products */; ProductGroup = 58C572571AA6236500CDF9C8 /* Products */;
ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */; ProjectRef = 587650F61A9EB120008B8F17 /* RCTText.xcodeproj */;
}, },
{
ProductGroup = 67C95F161B0E647A0040BCE2 /* Products */;
ProjectRef = 67C95F151B0E647A0040BCE2 /* RCTWebSocket.xcodeproj */;
},
{ {
ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */; ProductGroup = 14A2D43D1AC3E41A00CC738A /* Products */;
ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */; ProjectRef = 14A2D43C1AC3E41A00CC738A /* React.xcodeproj */;
@ -276,6 +299,13 @@
remoteRef = 58C5725A1AA6236500CDF9C8 /* PBXContainerItemProxy */; remoteRef = 58C5725A1AA6236500CDF9C8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR; sourceTree = BUILT_PRODUCTS_DIR;
}; };
67C95F1E1B0E647A0040BCE2 /* libRCTWebSocket.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTWebSocket.a;
remoteRef = 67C95F1D1B0E647A0040BCE2 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */ /* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */

View File

@ -237,10 +237,31 @@ var SearchScreen = React.createClass({
return <ActivityIndicatorIOS style={styles.scrollSpinner} />; return <ActivityIndicatorIOS style={styles.scrollSpinner} />;
}, },
renderRow: function(movie: Object) { renderSeparator: function(
sectionID: number | string,
rowID: number | string,
adjacentRowHighlighted: boolean
) {
var style = styles.rowSeparator;
if (adjacentRowHighlighted) {
style = [style, styles.rowSeparatorHide];
}
return (
<View key={"SEP_" + sectionID + "_" + rowID} style={style}/>
);
},
renderRow: function(
movie: Object,
sectionID: number | string,
rowID: number | string,
highlightRowFunc: (sectionID: ?number | string, rowID: ?number | string) => void,
) {
return ( return (
<MovieCell <MovieCell
onSelect={() => this.selectMovie(movie)} onSelect={() => this.selectMovie(movie)}
onHighlight={() => highlightRowFunc(sectionID, rowID)}
onUnhighlight={() => highlightRowFunc(null, null)}
movie={movie} movie={movie}
/> />
); );
@ -254,6 +275,7 @@ var SearchScreen = React.createClass({
/> : /> :
<ListView <ListView
ref="listview" ref="listview"
renderSeparator={this.renderSeparator}
dataSource={this.state.dataSource} dataSource={this.state.dataSource}
renderFooter={this.renderFooter} renderFooter={this.renderFooter}
renderRow={this.renderRow} renderRow={this.renderRow}
@ -352,6 +374,14 @@ var styles = StyleSheet.create({
scrollSpinner: { scrollSpinner: {
marginVertical: 20, marginVertical: 20,
}, },
rowSeparator: {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
height: 1,
marginLeft: 4,
},
rowSeparatorHide: {
opacity: 0.0,
},
}); });
module.exports = SearchScreen; module.exports = SearchScreen;

View File

@ -70,6 +70,14 @@ var TouchableHighlight = React.createClass({
*/ */
underlayColor: React.PropTypes.string, underlayColor: React.PropTypes.string,
style: View.propTypes.style, style: View.propTypes.style,
/**
* Called immediately after the underlay is shown
*/
onShowUnderlay: React.PropTypes.func,
/**
* Called immediately after the underlay is hidden
*/
onHideUnderlay: React.PropTypes.func,
}, },
mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin], mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin],
@ -159,6 +167,7 @@ var TouchableHighlight = React.createClass({
_showUnderlay: function() { _showUnderlay: function() {
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps); this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
this.props.onShowUnderlay && this.props.onShowUnderlay();
}, },
_hideUnderlay: function() { _hideUnderlay: function() {
@ -170,6 +179,7 @@ var TouchableHighlight = React.createClass({
...INACTIVE_UNDERLAY_PROPS, ...INACTIVE_UNDERLAY_PROPS,
style: this.state.underlayStyle, style: this.state.underlayStyle,
}); });
this.props.onHideUnderlay && this.props.onHideUnderlay();
} }
}, },

View File

@ -108,7 +108,7 @@ var ListView = React.createClass({
* You must provide a renderRow function. If you omit any of the other render * You must provide a renderRow function. If you omit any of the other render
* functions, ListView will simply skip rendering them. * functions, ListView will simply skip rendering them.
* *
* - renderRow(rowData, sectionID, rowID); * - renderRow(rowData, sectionID, rowID, highlightRow);
* - renderSectionHeader(sectionData, sectionID); * - renderSectionHeader(sectionData, sectionID);
*/ */
propTypes: { propTypes: {
@ -116,11 +116,22 @@ var ListView = React.createClass({
dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired,
/** /**
* (rowData, sectionID, rowID) => renderable * (sectionID, rowID, adjacentRowHighlighted) => renderable
* If provided, a renderable component to be rendered as the separator
* below each row but not the last row if there is a section header below.
* Take a sectionID and rowID of the row above and whether its adjacent row
* is highlighted.
*/
renderSeparator: PropTypes.func,
/**
* (rowData, sectionID, rowID, highlightRow) => renderable
* Takes a data entry from the data source and its ids and should return * Takes a data entry from the data source and its ids and should return
* a renderable component to be rendered as the row. By default the data * a renderable component to be rendered as the row. By default the data
* is exactly what was put into the data source, but it's also possible to * is exactly what was put into the data source, but it's also possible to
* provide custom extractors. * provide custom extractors. ListView can be notified when a row is
* being highlighted by calling highlightRow function. The separators above and
* below will be hidden when a row is highlighted. The highlighted state of
* a row can be reset by calling highlightRow(null).
*/ */
renderRow: PropTypes.func.isRequired, renderRow: PropTypes.func.isRequired,
/** /**
@ -227,6 +238,7 @@ var ListView = React.createClass({
return { return {
curRenderedRowsCount: this.props.initialListSize, curRenderedRowsCount: this.props.initialListSize,
prevRenderedRowsCount: 0, prevRenderedRowsCount: 0,
highlightedRow: {},
}; };
}, },
@ -256,6 +268,10 @@ var ListView = React.createClass({
} }
}, },
onRowHighlighted: function(sectionID, rowID) {
this.setState({highlightedRow: {sectionID, rowID}});
},
render: function() { render: function() {
var bodyComponents = []; var bodyComponents = [];
@ -305,11 +321,28 @@ var ListView = React.createClass({
null, null,
dataSource.getRowData(sectionIdx, rowIdx), dataSource.getRowData(sectionIdx, rowIdx),
sectionID, sectionID,
rowID rowID,
this.onRowHighlighted
)} )}
/>; />;
bodyComponents.push(row); bodyComponents.push(row);
totalIndex++; totalIndex++;
if (this.props.renderSeparator &&
(rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) {
var adjacentRowHighlighted =
this.state.highlightedRow.sectionID === sectionID && (
this.state.highlightedRow.rowID === rowID ||
this.state.highlightedRow.rowID === rowIDs[rowIdx + 1]
);
var separator = this.props.renderSeparator(
sectionID,
rowID,
adjacentRowHighlighted
);
bodyComponents.push(separator);
totalIndex++;
}
if (++rowCount === this.state.curRenderedRowsCount) { if (++rowCount === this.state.curRenderedRowsCount) {
break; break;
} }