This was my first React native project for a property development company and the task was to highlight the selected building floor on a wheel picker scroll. (By the way, you can check the working example of this APP (IOS, Android), for now, the language is only Georgian.) U also can download this package from NPM
But there was no hope of finding any react-native package or StackOverflow help. All custom pickers and IOS native pickers too are making callback only on scroll end. I always try to write own code and not use packages, but this time I thought that is was a difficult task which will take a lot of time. The hours and energy spent on the search told me that I had to do everything by myself. Fortunately, many React Native developers were looking for similar functionality and in their google footsteps, I found the react-native-swipe-picker package, where FlatList or ScrollView have been used as a picker, so this was a chance to fix my issue.
I have added scroll callbacks, fixed some bugs, and improved the functionality, make it more convenient for developers.
A simple example of how to use DynamicallySelectedPicker component
import React, {useState} from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import DynamicallySelectedPicker from './src/components/DynamicallySelectedPicker';
const App = () => {
const [selectedValue, setSelectedValue] = useState(0);
return (
<View style={styles.body}>
<View style={{margin: 30}}>
<Text>Item index {selectedValue}</Text>
</View>
<DynamicallySelectedPicker
items={[
{
value: 1,
label: 'Item 1',
},
{
value: 2,
label: 'Item 2',
},
{
value: 3,
label: 'Item 3',
},
{
value: 4,
label: 'Item 4',
},
{
value: 5,
label: 'Item 5',
},
]}
width={300}
height={300}
onScroll={(selected) => setSelectedValue(selected.index)}
/>
</View>
);
};
const styles = StyleSheet.create({
body: {
backgroundColor: Colors.white,
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
This is a React Native example with one big component (it could be separated into little functional components). To run it with Expo you have to change
react-native-linear-gradient package to expo-linear-gradient
import React from 'react';
import PropTypes from 'prop-types';
import {StyleSheet, View, ScrollView, Platform, Text} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import PickerListItem from './PickerListItem';
export default class DynamicallySelectedPicker extends React.Component {
constructor(props) {
super(props);
// set picker item height for android and ios
const {height, transparentItemRows, initialSelectedIndex} = props;
let itemHeight = height / (transparentItemRows * 2 + 1);
// In ios we have to manually ceil items height to eliminate distortion in the visualization, when we have big data.
if (Platform.OS === 'ios') {
itemHeight = Math.ceil(itemHeight);
}
this.state = {
itemHeight: itemHeight,
itemIndex: initialSelectedIndex,
};
}
/**
* Generate fake items for picker top and bottom.
* @param n
* @returns {[]}
*/
fakeItems(n = 3) {
const itemsArr = [];
for (let i = 0; i < n; i++) {
itemsArr[i] = {
value: -1,
label: '',
};
}
return itemsArr;
}
/**
* Get extended picker items length.
* @returns {number}
*/
allItemsLength() {
return this.extendedItems().length - this.props.transparentItemRows * 2;
}
/**
*
* @param event
*/
onScroll(event) {
const {items, onScroll} = this.props;
const tempIndex = this.getItemTemporaryIndex(event);
if (
this.state.itemIndex !== tempIndex &&
tempIndex >= 0 &&
tempIndex < this.allItemsLength()
) {
this.setItemIndex(tempIndex);
onScroll({index: tempIndex, item: items[tempIndex]});
}
}
/**
*
* @param event
* @returns {number}
*/
getItemTemporaryIndex(event) {
return Math.round(
event.nativeEvent.contentOffset.y / this.state.itemHeight,
);
}
/**
*
* @param index
*/
setItemIndex(index) {
this.setState({
itemIndex: index,
});
}
/**
* Add fake items to make picker almost like IOS native wheel picker.
* @returns {*[]}
*/
extendedItems() {
const {transparentItemRows} = this.props;
return [
...this.fakeItems(transparentItemRows),
...this.props.items,
...this.fakeItems(transparentItemRows),
];
}
/**
*
* @param item
* @param index
* @returns {*}
*/
renderPickerListItem(item, index) {
const {itemHeight} = this.state;
const {allItemsColor, itemColor} = this.props;
return (
<View
key={index}
style={[
styles.listItem,
{
height: itemHeight,
},
]}>
<Text
style={{
color: itemColor ? itemColor : allItemsColor,
}}>
{item.label}
</Text>
</View>
);
}
render() {
const {itemIndex, itemHeight} = this.state;
const {
width,
height,
topGradientColors,
bottomGradientColors,
selectedItemBorderColor,
transparentItemRows,
} = this.props;
return (
<View style={{height: height, width: width}}>
<ScrollView
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
onScroll={(event) => {
this.onScroll(event);
}}
scrollEventThrottle
initialScrollIndex={itemIndex}
snapToInterval={itemHeight}>
{this.extendedItems().map((item, index) => {
return this.renderPickerListItem(item, index);
})}
</ScrollView>
<View
style={[
styles.gradientWrapper,
{
top: 0,
borderBottomWidth: 1,
borderBottomColor: selectedItemBorderColor,
},
]}
pointerEvents="none">
<LinearGradient
colors={topGradientColors}
style={[
styles.pickerGradient,
{
height: transparentItemRows * itemHeight,
},
]}
/>
</View>
<View
style={[
styles.gradientWrapper,
{
bottom: 0,
borderTopWidth: 1,
borderTopColor: selectedItemBorderColor,
},
]}
pointerEvents="none">
<LinearGradient
colors={bottomGradientColors}
style={[
styles.pickerGradient,
{height: transparentItemRows * itemHeight},
]}
/>
</View>
</View>
);
}
}
DynamicallySelectedPicker.defaultProps = {
items: [{value: 0, label: 'No items', itemColor: 'red'}],
onScroll: () => {},
width: 300,
height: 300,
initialSelectedIndex: 0,
transparentItemRows: 3,
allItemsColor: '#000',
selectedItemBorderColor: '#cecece',
topGradientColors: [
'rgba( 255, 255, 255, 1 )',
'rgba( 255, 255, 255, 0.9 )',
'rgba( 255, 255, 255, 0.7 )',
'rgba( 255, 255, 255, 0.5 )',
],
bottomGradientColors: [
'rgba( 255, 255, 255, 0.5 )',
'rgba( 255, 255, 255, 0.7 )',
'rgba( 255, 255, 255, 0.9 )',
'rgba( 255, 255, 255, 1 )',
],
};
DynamicallySelectedPicker.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.string,
itemColor: PropTypes.string,
}),
),
onScroll: PropTypes.func,
initialSelectedIndex: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
allItemsColor: PropTypes.string,
selectedItemBorderColor: PropTypes.string,
topGradientColors: PropTypes.array,
bottomGradientColors: PropTypes.array,
};
const styles = StyleSheet.create({
listItem: {
alignItems: 'center',
justifyContent: 'center',
},
gradientWrapper: {
position: 'absolute',
width: '100%',
},
pickerGradient: {
width: '100%',
},
});
I hope someone will use it and won’t waste time like me. Also if u have questions or remarks, please, feel free to communicate.
Contribution or issues reports in the GitHub repository (It’s a little bit different, with more props and callbacks.) would be great.
Sorry again for my bad English and thank you.
Top comments (0)