-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
Reviewed By: sahrens Differential Revision: D5912488 fbshipit-source-id: 3d2872a7712c00badcbd8341a7d058df14a9091a
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule SwipeableFlatList | ||
* @flow | ||
* @format | ||
*/ | ||
'use strict'; | ||
|
||
import type {Props as FlatListProps} from 'FlatList'; | ||
import type {renderItemType} from 'VirtualizedList'; | ||
|
||
const PropTypes = require('prop-types'); | ||
const React = require('React'); | ||
const SwipeableRow = require('SwipeableRow'); | ||
const FlatList = require('FlatList'); | ||
|
||
type SwipableListProps = { | ||
/** | ||
* To alert the user that swiping is possible, the first row can bounce | ||
* on component mount. | ||
*/ | ||
bounceFirstRowOnMount: boolean, | ||
// Maximum distance to open to after a swipe | ||
maxSwipeDistance: number | (Object => number), | ||
// Callback method to render the view that will be unveiled on swipe | ||
renderQuickActions: renderItemType, | ||
}; | ||
|
||
type Props<ItemT> = SwipableListProps & FlatListProps<ItemT>; | ||
|
||
type State = { | ||
openRowKey: ?string, | ||
}; | ||
|
||
/** | ||
* A container component that renders multiple SwipeableRow's in a FlatList | ||
* implementation. This is designed to be a drop-in replacement for the | ||
* standard React Native `FlatList`, so use it as if it were a FlatList, but | ||
* with extra props, i.e. | ||
* | ||
* <SwipeableListView renderRow={..} renderQuickActions={..} {..FlatList props} /> | ||
* | ||
* SwipeableRow can be used independently of this component, but the main | ||
* benefit of using this component is | ||
* | ||
* - It ensures that at most 1 row is swiped open (auto closes others) | ||
* - It can bounce the 1st row of the list so users know it's swipeable | ||
* - Increase performance on iOS by locking list swiping when row swiping is occuring | ||
* - More to come | ||
*/ | ||
|
||
class SwipeableFlatList<ItemT> extends React.Component<Props<ItemT>, State> { | ||
props: Props<ItemT>; | ||
state: State; | ||
|
||
_flatListRef: ?FlatList<ItemT> = null; | ||
_shouldBounceFirstRowOnMount: boolean = false; | ||
|
||
static propTypes = { | ||
...FlatList.propTypes, | ||
|
||
/** | ||
* To alert the user that swiping is possible, the first row can bounce | ||
* on component mount. | ||
*/ | ||
bounceFirstRowOnMount: PropTypes.bool.isRequired, | ||
|
||
// Maximum distance to open to after a swipe | ||
maxSwipeDistance: PropTypes.oneOfType([PropTypes.number, PropTypes.func]) | ||
.isRequired, | ||
|
||
// Callback method to render the view that will be unveiled on swipe | ||
renderQuickActions: PropTypes.func.isRequired, | ||
}; | ||
|
||
static defaultProps = { | ||
...FlatList.defaultProps, | ||
bounceFirstRowOnMount: true, | ||
renderQuickActions: () => null, | ||
}; | ||
|
||
constructor(props: Props<ItemT>, context: any): void { | ||
super(props, context); | ||
this.state = { | ||
openRowKey: null, | ||
}; | ||
|
||
this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount; | ||
} | ||
|
||
render(): React.Node { | ||
return ( | ||
<FlatList | ||
{...this.props} | ||
ref={ref => { | ||
this._flatListRef = ref; | ||
}} | ||
onScroll={this._onScroll} | ||
renderItem={this._renderItem} | ||
/> | ||
); | ||
} | ||
|
||
_onScroll = (e): void => { | ||
// Close any opens rows on ListView scroll | ||
if (this.state.openRowKey) { | ||
this.setState({ | ||
openRowKey: null, | ||
}); | ||
} | ||
|
||
this.props.onScroll && this.props.onScroll(e); | ||
}; | ||
|
||
_renderItem = (info: Object): ?React.Element<any> => { | ||
const slideoutView = this.props.renderQuickActions(info); | ||
const key = this.props.keyExtractor(info.item, info.index); | ||
|
||
// If renderQuickActions is unspecified or returns falsey, don't allow swipe | ||
if (!slideoutView) { | ||
return this.props.renderItem(info); | ||
} | ||
|
||
let shouldBounceOnMount = false; | ||
if (this._shouldBounceFirstRowOnMount) { | ||
this._shouldBounceFirstRowOnMount = false; | ||
shouldBounceOnMount = true; | ||
} | ||
|
||
return ( | ||
<SwipeableRow | ||
slideoutView={slideoutView} | ||
isOpen={key === this.state.openRowKey} | ||
maxSwipeDistance={this._getMaxSwipeDistance(info)} | ||
onOpen={() => this._onOpen(key)} | ||
onClose={() => this._onClose(key)} | ||
shouldBounceOnMount={shouldBounceOnMount} | ||
onSwipeEnd={this._setListViewScrollable} | ||
onSwipeStart={this._setListViewNotScrollable}> | ||
{this.props.renderItem(info)} | ||
</SwipeableRow> | ||
); | ||
}; | ||
|
||
// This enables rows having variable width slideoutView. | ||
_getMaxSwipeDistance(info: Object): number { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
tomasreimers
Contributor
|
||
if (typeof this.props.maxSwipeDistance === 'function') { | ||
return this.props.maxSwipeDistance(info); | ||
} | ||
|
||
return this.props.maxSwipeDistance; | ||
} | ||
|
||
_setListViewScrollableTo(value: boolean) { | ||
if (this._flatListRef) { | ||
this._flatListRef.setNativeProps({ | ||
scrollEnabled: value, | ||
}); | ||
} | ||
} | ||
|
||
_setListViewScrollable = () => { | ||
this._setListViewScrollableTo(true); | ||
}; | ||
|
||
_setListViewNotScrollable = () => { | ||
this._setListViewScrollableTo(false); | ||
}; | ||
|
||
_onOpen(key: any): void { | ||
this.setState({ | ||
openRowKey: key, | ||
}); | ||
} | ||
|
||
_onClose(key: any): void { | ||
this.setState({ | ||
openRowKey: null, | ||
}); | ||
} | ||
} | ||
|
||
module.exports = SwipeableFlatList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule SwipeableFlatListExample | ||
* @flow | ||
* @format | ||
*/ | ||
'use strict'; | ||
|
||
const React = require('react'); | ||
const createReactClass = require('create-react-class'); | ||
const ReactNative = require('react-native'); | ||
const { | ||
Image, | ||
SwipeableFlatList, | ||
TouchableHighlight, | ||
StyleSheet, | ||
Text, | ||
View, | ||
Alert, | ||
} = ReactNative; | ||
|
||
const RNTesterPage = require('./RNTesterPage'); | ||
|
||
const data = [ | ||
{ | ||
key: 'like', | ||
icon: require('./Thumbnails/like.png'), | ||
data: 'Like!', | ||
}, | ||
{ | ||
key: 'heart', | ||
icon: require('./Thumbnails/heart.png'), | ||
data: 'Heart!', | ||
}, | ||
{ | ||
key: 'party', | ||
icon: require('./Thumbnails/party.png'), | ||
data: 'Party!', | ||
}, | ||
]; | ||
|
||
const SwipeableFlatListExample = createReactClass({ | ||
displayName: 'SwipeableFlatListExample', | ||
statics: { | ||
title: '<SwipeableFlatList>', | ||
description: 'Performant, scrollable, swipeable list of data.', | ||
}, | ||
|
||
render: function() { | ||
return ( | ||
<RNTesterPage | ||
title={this.props.navigator ? null : '<SwipeableListView>'} | ||
noSpacer={true} | ||
noScroll={true}> | ||
<SwipeableFlatList | ||
data={data} | ||
bounceFirstRowOnMount={true} | ||
maxSwipeDistance={160} | ||
renderItem={this._renderItem.bind(this)} | ||
renderQuickActions={this._renderQuickActions.bind(this)} | ||
/> | ||
</RNTesterPage> | ||
); | ||
}, | ||
|
||
_renderItem: function({item}): ?React.Element<any> { | ||
return ( | ||
<View style={styles.row}> | ||
<Image style={styles.rowIcon} source={item.icon} /> | ||
<View style={styles.rowData}> | ||
<Text style={styles.rowDataText}> | ||
{item.data} | ||
</Text> | ||
</View> | ||
</View> | ||
); | ||
}, | ||
|
||
_renderQuickActions: function({item}: Object): ?React.Element<any> { | ||
return ( | ||
<View style={styles.actionsContainer}> | ||
<TouchableHighlight | ||
style={styles.actionButton} | ||
onPress={() => { | ||
Alert.alert( | ||
'Tips', | ||
'You could do something with this edit action!', | ||
); | ||
}}> | ||
<Text style={styles.actionButtonText}>Edit</Text> | ||
</TouchableHighlight> | ||
<TouchableHighlight | ||
style={[styles.actionButton, styles.actionButtonDestructive]} | ||
onPress={() => { | ||
Alert.alert( | ||
'Tips', | ||
'You could do something with this remove action!', | ||
); | ||
}}> | ||
<Text style={styles.actionButtonText}>Remove</Text> | ||
</TouchableHighlight> | ||
</View> | ||
); | ||
}, | ||
}); | ||
|
||
var styles = StyleSheet.create({ | ||
row: { | ||
flexDirection: 'row', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
padding: 10, | ||
backgroundColor: '#F6F6F6', | ||
}, | ||
rowIcon: { | ||
width: 64, | ||
height: 64, | ||
marginRight: 20, | ||
}, | ||
rowData: { | ||
flex: 1, | ||
}, | ||
rowDataText: { | ||
fontSize: 24, | ||
}, | ||
actionsContainer: { | ||
flex: 1, | ||
flexDirection: 'row', | ||
justifyContent: 'flex-end', | ||
alignItems: 'center', | ||
}, | ||
actionButton: { | ||
padding: 10, | ||
width: 80, | ||
backgroundColor: '#999999', | ||
}, | ||
actionButtonDestructive: { | ||
backgroundColor: '#FF0000', | ||
}, | ||
actionButtonText: { | ||
textAlign: 'center', | ||
}, | ||
}); | ||
|
||
module.exports = SwipeableFlatListExample; |
6 comments
on commit d8cc6e3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello! @tomasreimers
Awesome PR. Thanks for this!
Can you also do support for SectionList
?
Because I have SwipeableListView
with sections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also when you swipe item - others items not closed. Which they should.
It ensures that at most 1 row is swiped open (auto closes others)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kesha-antonov, The current version of the component is pretty broken due rerenders flow and optimizations, so it's not updated when it should(and/or) updated when it shouldn't :). The workaround I found is to fork this and add extraData={this.state}
to FlatList props and make a wrapper around SwipeableRow with correct shouldComponentUpate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AntonPuko #16682
Already did it 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the incredibly late response!
@AntonPuko , would love to see a PR if you have one!
@kesha-antonov added the two as tasks for myself, although I've been handling other tasks. If you end up writing it, would love to incorporate the changes, otherwise I'll probably get around to it when the latest project is done! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomasreimers Hi, not sure if I'll have time to create PR nearly soon, but here is my version, feel free to grab if you want:
https://github.com/Brewskey/Brewskey.App/blob/v2/src/common/SwipeableFlatList/index.js
It has a few extra stuff like _onRefresh and hardcoded onEndReachedThreshold, that is project dependent and doesn't exist in the official version and can be removed, but in other places looks cleaner.
I think with current official swipeableRow all rows still will be re-rendered every time but its lack of SwipeableRow component, It works well if we use PureComponents for SwipeableRows, something like that: https://github.com/Brewskey/Brewskey.App/blob/v2/src/common/SwipeableLoaderRow.js
this function needs to return a number which is really a problem because each quick action needs to be of a fixed width. curios to know how you guys pass that number @sahrens. is it static number for you guys or you guys do some layout first?