├── .gitignore ├── LICENSE ├── index.js ├── package.json ├── readme.md └── res └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | .DS_Store 4 | .swp 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 veizz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, {Component} from 'react'; 4 | import ReactNative, { 5 | StyleSheet, 6 | Text, 7 | View, 8 | ScrollView, 9 | Dimensions, 10 | Platform, 11 | ViewPropTypes 12 | } from 'react-native'; 13 | import PropTypes from 'prop-types'; 14 | 15 | const deviceWidth = Dimensions.get('window').width; 16 | const deviceHeight = Dimensions.get('window').height; 17 | 18 | export default class ScrollPicker extends Component { 19 | 20 | static propTypes = { 21 | style:ViewPropTypes.style, 22 | dataSource:PropTypes.array.isRequired, 23 | selectedIndex:PropTypes.number, 24 | animateToSelectedIndex:PropTypes.bool, 25 | onValueChange:PropTypes.func, 26 | renderItem:PropTypes.func, 27 | highlightColor:PropTypes.string, 28 | 29 | itemHeight:PropTypes.number, 30 | wrapperHeight:PropTypes.number, 31 | wrapperColor:PropTypes.string, 32 | }; 33 | 34 | constructor(props){ 35 | super(props); 36 | 37 | this.itemHeight = this.props.itemHeight || 30; 38 | this.wrapperHeight = this.props.wrapperHeight || (this.props.style ? this.props.style.height : 0) ||this.itemHeight * 5; 39 | 40 | this.state = { 41 | selectedIndex: this.props.selectedIndex || 0 42 | }; 43 | } 44 | 45 | componentDidMount(){ 46 | if(this.props.selectedIndex){ 47 | setTimeout(() => { 48 | this.scrollToIndex(this.props.selectedIndex, this.props.animateToSelectedIndex); 49 | }, 0); 50 | } 51 | } 52 | componentWillUnmount(){ 53 | this.timer && clearTimeout(this.timer); 54 | } 55 | 56 | render(){ 57 | let {header, footer} = this._renderPlaceHolder(); 58 | let highlightWidth = (this.props.style ? this.props.style.width : 0) || deviceWidth; 59 | let highlightColor = this.props.highlightColor || '#333'; 60 | let wrapperStyle = { 61 | height:this.wrapperHeight, 62 | flex:1, 63 | backgroundColor:this.props.wrapperColor ||'#fafafa', 64 | overflow:'hidden', 65 | }; 66 | 67 | let highlightStyle = { 68 | position:'absolute', 69 | top:(this.wrapperHeight - this.itemHeight) / 2, 70 | height:this.itemHeight, 71 | width:highlightWidth, 72 | borderTopColor:highlightColor, 73 | borderBottomColor:highlightColor, 74 | borderTopWidth:StyleSheet.hairlineWidth, 75 | borderBottomWidth:StyleSheet.hairlineWidth, 76 | }; 77 | 78 | return ( 79 | 80 | 81 | { this.sview = sview; }} 83 | bounces={false} 84 | showsVerticalScrollIndicator={false} 85 | nestedScrollEnabled={true} 86 | onMomentumScrollBegin={this._onMomentumScrollBegin.bind(this)} 87 | onMomentumScrollEnd={this._onMomentumScrollEnd.bind(this)} 88 | onScrollBeginDrag={this._onScrollBeginDrag.bind(this)} 89 | onScrollEndDrag={this._onScrollEndDrag.bind(this)} 90 | > 91 | {header} 92 | {this.props.dataSource.map(this._renderItem.bind(this))} 93 | {footer} 94 | 95 | 96 | ) 97 | } 98 | 99 | _renderPlaceHolder(){ 100 | let h = (this.wrapperHeight - this.itemHeight) / 2; 101 | let header = ; 102 | let footer = ; 103 | return {header, footer}; 104 | } 105 | 106 | _renderItem(data, index){ 107 | let isSelected = index === this.state.selectedIndex; 108 | let item = {data}; 109 | 110 | if(this.props.renderItem){ 111 | item = this.props.renderItem(data, index, isSelected); 112 | } 113 | 114 | return ( 115 | 116 | {item} 117 | 118 | ); 119 | } 120 | _scrollFix(e){ 121 | let y = 0; 122 | let h = this.itemHeight; 123 | if(e.nativeEvent.contentOffset){ 124 | y = e.nativeEvent.contentOffset.y; 125 | } 126 | let selectedIndex = Math.round(y / h); 127 | let _y = selectedIndex * h; 128 | if(_y !== y){ 129 | // using scrollTo in ios, onMomentumScrollEnd will be invoked 130 | if(Platform.OS === 'ios'){ 131 | this.isScrollTo = true; 132 | } 133 | this.sview.scrollTo({y:_y}); 134 | } 135 | if(this.state.selectedIndex === selectedIndex){ 136 | return; 137 | } 138 | // onValueChange 139 | if(this.props.onValueChange){ 140 | let selectedValue = this.props.dataSource[selectedIndex]; 141 | this.setState({ 142 | selectedIndex:selectedIndex, 143 | }); 144 | this.props.onValueChange(selectedValue, selectedIndex); 145 | } 146 | } 147 | _onScrollBeginDrag(){ 148 | this.dragStarted = true; 149 | if(Platform.OS === 'ios'){ 150 | this.isScrollTo = false; 151 | } 152 | this.timer && clearTimeout(this.timer); 153 | } 154 | _onScrollEndDrag(e){ 155 | this.dragStarted = false; 156 | // if not used, event will be garbaged 157 | let _e = { 158 | nativeEvent:{ 159 | contentOffset:{ 160 | y: e.nativeEvent.contentOffset.y, 161 | }, 162 | }, 163 | }; 164 | this.timer && clearTimeout(this.timer); 165 | this.timer = setTimeout( 166 | () => { 167 | if(!this.momentumStarted && !this.dragStarted){ 168 | this._scrollFix(_e, 'timeout'); 169 | } 170 | }, 171 | 10 172 | ); 173 | } 174 | _onMomentumScrollBegin(e){ 175 | this.momentumStarted = true; 176 | this.timer && clearTimeout(this.timer); 177 | } 178 | _onMomentumScrollEnd(e){ 179 | this.momentumStarted = false; 180 | if(!this.isScrollTo && !this.momentumStarted && !this.dragStarted){ 181 | this._scrollFix(e); 182 | } 183 | } 184 | 185 | scrollToIndex(ind, animated = true){ 186 | this.setState({ 187 | selectedIndex:ind, 188 | }); 189 | let y = this.itemHeight * ind; 190 | this.sview.scrollTo({y:y, animated}); 191 | } 192 | 193 | getSelected(){ 194 | let selectedIndex = this.state.selectedIndex; 195 | let selectedValue = this.props.dataSource[selectedIndex]; 196 | return selectedValue; 197 | } 198 | } 199 | 200 | let styles = StyleSheet.create({ 201 | itemWrapper: { 202 | height:30, 203 | justifyContent: 'center', 204 | alignItems: 'center', 205 | }, 206 | itemText:{ 207 | color:'#999', 208 | }, 209 | itemTextSelected:{ 210 | color:'#333', 211 | }, 212 | }); 213 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-picker-scrollview", 3 | "version": "1.0.1", 4 | "description": "a pure js picker, each option item customizable", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/veizz/react-native-picker-scrollview.git" 12 | }, 13 | "keywords": [ 14 | "picker", 15 | "react-native", 16 | "react-native-picker" 17 | ], 18 | "author": "veizz", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/veizz/react-native-picker-scrollview/issues" 22 | }, 23 | "homepage": "https://github.com/veizz/react-native-picker-scrollview#readme" 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## react-native-picker-scrollview 2 | 3 | a pure js picker, each option item customizable 4 | 5 | ![example](./res/demo.gif) 6 | 7 | 8 | ### usage 9 | 10 | ```shell 11 | npm install react-native-picker-scrollview --save 12 | ``` 13 | 14 | ```jsx 15 | import React, {Component} from 'react'; 16 | import {View, Text} from 'react-native'; 17 | import ScrollPicker from 'react-native-picker-scrollview'; 18 | 19 | export default class SimpleExample extends Component { 20 | 21 | render() { 22 | return( 23 | {this.sp = sp}} 25 | 26 | dataSource={[ 27 | 'a', 28 | 'b', 29 | 'c', 30 | 'd', 31 | ]} 32 | selectedIndex={0} 33 | itemHeight={50} 34 | wrapperHeight={250} 35 | wrapperColor={'#ffffff'} 36 | highlightColor={'#d8d8d8'} 37 | renderItem={(data, index, isSelected) => { 38 | return( 39 | 40 | {data} 41 | 42 | ) 43 | }} 44 | onValueChange={(data, selectedIndex) => { 45 | // 46 | }} 47 | /> 48 | ) 49 | } 50 | 51 | 52 | // 53 | someOtherFunc(){ 54 | this.sp.scrollToIndex(2); // select 'c' 55 | let selectedValue = this.sp.getSelected(); // returns 'c' 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /res/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veizz/react-native-picker-scrollview/7d8d7ec37ee7cb4094ab4d0a69673fc9f0b4b705/res/demo.gif --------------------------------------------------------------------------------