Created
December 14, 2017 12:06
-
-
Save jevakallio/f9d704d36786b9d5dae49028f0238f67 to your computer and use it in GitHub Desktop.
React Native Horizontal Picker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
// https://twitter.com/jevakallio/status/941258932529614848 | |
import React, { Component, type Node } from 'react'; | |
import styled from 'styled-components/native'; | |
import Touchable from 'react-native-platform-touchable'; | |
import Carousel from 'react-native-snap-carousel'; | |
// $FlowFixMe | |
import { LinearGradient } from 'expo'; | |
const Container = styled.View``; | |
// react-native-snap-carousel doesn't support setting different opacities | |
// for inactive items based on their distance from the active item, so we'll | |
// fake it here by fading the view in and out with gradients | |
const StartGradient = styled(LinearGradient).attrs({ | |
pointerEvents: 'none', | |
colors: ['rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0)'], | |
start: [0, 0.8, 1], | |
end: [1, 0.8, 1] | |
})` | |
width: ${p => p.width + 10}px; | |
height: ${p => p.width}px; | |
position: absolute; | |
top: 0; | |
left: 0; | |
`; | |
const EndGradient = styled(LinearGradient).attrs({ | |
pointerEvents: 'none', | |
colors: ['rgba(242, 242, 242, 0)', 'rgba(242, 242, 242, 0.6)', 'rgba(242, 242, 242, 0.6)'], | |
start: [0, 0.8, 1], | |
end: [1, 0.8, 1] | |
})` | |
width: ${p => p.width + 10}px; | |
height: ${p => p.width}px; | |
position: absolute; | |
top: 0; | |
right: 0; | |
`; | |
// show a ring around the focused item | |
const FocusCircle = styled.View.attrs({ pointerEvents: 'none' })` | |
position: absolute; | |
top: 0; | |
left: ${p => p.width * p.offset}; | |
bottom: 0; | |
width: ${p => p.width}; | |
height: ${p => p.width}; | |
border-radius: ${p => p.width}; | |
border-color: white; | |
border-width: 2; | |
`; | |
// when an item is touched, we should snap to it | |
const ItemWrapper = styled(Touchable).attrs({ | |
activeOpacity: 0.8, | |
background: Touchable.SelectableBackgroundBorderless() | |
})` | |
background-color: transparent; | |
width: ${p => p.width}px; | |
height: ${p => p.width}px; | |
align-items: center; | |
justify-content: center; | |
`; | |
type Props<T> = { | |
onItemSelected: (item: T) => void, | |
renderItem: (item: T) => Node, | |
items: T[], | |
initialItem: T, | |
itemWidth: number, | |
visibleItemCount: number | |
}; | |
export default class HorizontalPicker extends Component<Props<*>, *> { | |
carouselRef = null; | |
props: Props<*>; | |
static defaultProps = { | |
itemWidth: 60, | |
visibleItemCount: 5 | |
}; | |
moveToItem = (index: number) => { | |
if (this.carouselRef) { | |
this.carouselRef.snapToItem(index); | |
} | |
}; | |
onSnapToItem = (index: number) => { | |
this.props.onItemSelected(this.props.items[index]); | |
}; | |
renderItem = ({ item, index }: { item: *, index: number }) => { | |
return ( | |
<ItemWrapper width={this.props.itemWidth} onPress={() => this.moveToItem(index)}> | |
{this.props.renderItem(item)} | |
</ItemWrapper> | |
); | |
}; | |
render() { | |
const { items, initialItem, itemWidth, visibleItemCount } = this.props; | |
return ( | |
<Container> | |
<Carousel | |
ref={ref => { | |
this.carouselRef = ref; | |
}} | |
data={items} | |
firstItem={items.indexOf(initialItem)} | |
renderItem={this.renderItem} | |
onSnapToItem={this.onSnapToItem} | |
sliderWidth={visibleItemCount * itemWidth} | |
itemWidth={itemWidth} | |
activeSlideOffset={10} | |
inactiveSlideScale={0.8} | |
inactiveSlideShift={-16} | |
/> | |
<StartGradient width={itemWidth} /> | |
<FocusCircle width={itemWidth} offset={(visibleItemCount - 1) / 2} /> | |
<EndGradient width={itemWidth} /> | |
</Container> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you very much for this gist! You saved me from needing to do real programming!