├── .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 | 
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
--------------------------------------------------------------------------------