├── .gitignore ├── README.md ├── WheelCurvedPicker.android.js ├── WheelCurvedPicker.ios.js ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── zyu │ ├── ReactNativeWheelPickerPackage.java │ ├── ReactWheelCurvedPicker.java │ └── ReactWheelCurvedPickerManager.java ├── demo.gif ├── demo_android.gif ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | android/build 2 | *.iml 3 | android/.idea/workspace.xml 4 | android/.idea/copyright/profiles_settings.xml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-wheel-picker 2 | [![npm version](http://img.shields.io/npm/v/react-native-wheel-picker.svg?style=flat-square)](https://npmjs.org/package/react-native-wheel-picker "View this project on npm") 3 | [![npm version](http://img.shields.io/npm/dm/react-native-wheel-picker.svg?style=flat-square)](https://npmjs.org/package/react-native-wheel-picker "View this project on npm") 4 | 5 | ## Introduction 6 | Cross platform Picker component based on React-native. 7 | 8 | Since picker is originally supported by ios while Android only supports a ugly Spinner component. If you want to have the same user behaviour, you can use this. 9 | 10 | The android component is based on https://github.com/AigeStudio/WheelPicker which runs super fast and smoothly. It also supports curved effect which make it exactly the same looking and feel as the ios picker. 11 | ![](https://raw.githubusercontent.com/lesliesam/react-native-wheel-picker/master/demo.gif) 12 | ![](https://raw.githubusercontent.com/lesliesam/react-native-wheel-picker/master/demo_android.gif) 13 | 14 | ## How to use 15 | 16 | Run command 17 | 18 | For apps using RN 0.40 or higher, please run 19 | ``` 20 | npm i react-native-wheel-picker --save 21 | ``` 22 | For apps using RN 0.39 or less, please run 23 | ``` 24 | npm install --save --save-exact react-native-wheel-picker@1.1.2 25 | ``` 26 | Add in settings.gradle 27 | ``` 28 | include ':react-native-wheel-picker' 29 | project(':react-native-wheel-picker').projectDir = new File(settingsDir, '../node_modules/react-native-wheel-picker/android') 30 | ``` 31 | Add in app/build.gradle 32 | ``` 33 | compile project(':react-native-wheel-picker') 34 | ``` 35 | Modify MainApplication 36 | ``` 37 | import com.zyu.ReactNativeWheelPickerPackage; 38 | ...... 39 | 40 | protected List getPackages() { 41 | return Arrays.asList( 42 | new MainReactPackage(), new ReactNativeWheelPickerPackage() 43 | ); 44 | } 45 | ``` 46 | 47 | ## Example code 48 | ``` 49 | import React, { Component } from 'react'; 50 | import { 51 | Platform, 52 | StyleSheet, 53 | Text, 54 | View, 55 | } from 'react-native'; 56 | 57 | 58 | import Picker from 'react-native-wheel-picker' 59 | var PickerItem = Picker.Item; 60 | 61 | export default class App extends Component<{}> { 62 | 63 | constructor(props) { 64 | super(props); 65 | this.state = { 66 | selectedItem : 2, 67 | itemList: ['刘备', '张飞', '关羽', '赵云', '黄忠', '马超', '魏延', '诸葛亮'] 68 | }; 69 | } 70 | 71 | onPickerSelect (index) { 72 | this.setState({ 73 | selectedItem: index, 74 | }) 75 | } 76 | 77 | onAddItem = () => { 78 | var name = '司马懿' 79 | if (this.state.itemList.indexOf(name) == -1) { 80 | this.state.itemList.push(name) 81 | } 82 | this.setState({ 83 | selectedItem: this.state.itemList.indexOf(name), 84 | }) 85 | } 86 | 87 | render () { 88 | return ( 89 | 90 | 91 | Welcome to React Native! 92 | 93 | this.onPickerSelect(index)}> 97 | {this.state.itemList.map((value, i) => ( 98 | 99 | ))} 100 | 101 | 102 | 你最喜欢的是:{this.state.itemList[this.state.selectedItem]} 103 | 104 | 105 | 107 | 怎么没有司马懿? 108 | 109 | 110 | ); 111 | } 112 | } 113 | 114 | const styles = StyleSheet.create({ 115 | container: { 116 | flex: 1, 117 | justifyContent: 'center', 118 | alignItems: 'center', 119 | backgroundColor: '#1962dd', 120 | }, 121 | welcome: { 122 | fontSize: 20, 123 | textAlign: 'center', 124 | margin: 10, 125 | color: '#ffffff', 126 | }, 127 | instructions: { 128 | textAlign: 'center', 129 | color: '#333333', 130 | marginBottom: 5, 131 | }, 132 | }); 133 | ``` 134 | -------------------------------------------------------------------------------- /WheelCurvedPicker.android.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import PropTypes from 'prop-types' 5 | import { 6 | View, 7 | ColorPropType, 8 | requireNativeComponent, 9 | } from 'react-native'; 10 | 11 | const defaultItemStyle = { color: 'white', fontSize: 26 }; 12 | 13 | const WheelCurvedPickerNativeInterface = { 14 | name: 'WheelCurvedPicker', 15 | propTypes: { 16 | ...View.propTypes, 17 | data:PropTypes.array, 18 | textColor: ColorPropType, 19 | textSize: PropTypes.number, 20 | itemStyle: PropTypes.object, 21 | itemSpace: PropTypes.number, 22 | onValueChange: PropTypes.func, 23 | selectedValue: PropTypes.any, 24 | selectedIndex: PropTypes.number, 25 | } 26 | } 27 | 28 | const WheelCurvedPickerNative = requireNativeComponent('WheelCurvedPicker', WheelCurvedPickerNativeInterface); 29 | 30 | class WheelCurvedPicker extends React.Component { 31 | 32 | propTypes: { 33 | ...View.propTypes, 34 | 35 | data: PropTypes.array, 36 | 37 | textColor: ColorPropType, 38 | 39 | textSize: PropTypes.number, 40 | 41 | itemStyle: PropTypes.object, 42 | 43 | itemSpace: PropTypes.number, 44 | 45 | onValueChange: PropTypes.func, 46 | 47 | selectedValue: PropTypes.any, 48 | 49 | selectedIndex: PropTypes.number, 50 | } 51 | 52 | constructor(props){ 53 | super(props) 54 | this.state = this._stateFromProps(props) 55 | } 56 | 57 | static defaultProps = { 58 | itemStyle : {color:"white", fontSize:26}, 59 | itemSpace: 20 60 | } 61 | 62 | componentWillReceiveProps (props) { 63 | this.setState(this._stateFromProps(props)); 64 | } 65 | 66 | _stateFromProps (props) { 67 | var selectedIndex = 0; 68 | var items = []; 69 | React.Children.forEach(props.children, function (child, index) { 70 | if (child.props.value === props.selectedValue) { 71 | selectedIndex = index; 72 | } 73 | items.push({value: child.props.value, label: child.props.label}); 74 | }); 75 | 76 | var textSize = props.itemStyle.fontSize 77 | var textColor = props.itemStyle.color 78 | 79 | return {selectedIndex, items, textSize, textColor}; 80 | } 81 | 82 | _onValueChange = (e) => { 83 | if (this.props.onValueChange) { 84 | this.props.onValueChange(e.nativeEvent.data); 85 | } 86 | } 87 | 88 | render() { 89 | return ; 96 | } 97 | } 98 | 99 | class Item extends React.Component { 100 | propTypes: { 101 | value: React.PropTypes.any, // string or integer basically 102 | label: React.PropTypes.string, 103 | } 104 | 105 | render () { 106 | // These items don't get rendered directly. 107 | return null; 108 | } 109 | } 110 | 111 | WheelCurvedPicker.Item = Item; 112 | 113 | module.exports = WheelCurvedPicker; 114 | -------------------------------------------------------------------------------- /WheelCurvedPicker.ios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { 5 | View, 6 | } from 'react-native'; 7 | 8 | module.exports = View; 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | defaultConfig { 7 | minSdkVersion 16 8 | targetSdkVersion 22 9 | versionCode 1 10 | versionName "1.0" 11 | ndk { 12 | abiFilters "armeabi-v7a", "x86" 13 | } 14 | } 15 | } 16 | 17 | dependencies { 18 | compile fileTree(dir: 'libs', include: ['*.jar']) 19 | compile "cn.aigestudio.wheelpicker:WheelPicker:1.0.3" 20 | compile 'com.facebook.react:react-native:+' 21 | } 22 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/zyu/ReactNativeWheelPickerPackage.java: -------------------------------------------------------------------------------- 1 | package com.zyu; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Sam Yu 15 | */ 16 | public class ReactNativeWheelPickerPackage implements ReactPackage { 17 | @Override 18 | public List createNativeModules(ReactApplicationContext reactContext) { 19 | return Collections.emptyList(); 20 | } 21 | 22 | public List> createJSModules() { 23 | return Collections.emptyList(); 24 | } 25 | 26 | @Override 27 | public List createViewManagers(ReactApplicationContext reactContext) { 28 | return Arrays.asList( 29 | new ReactWheelCurvedPickerManager() 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/src/main/java/com/zyu/ReactWheelCurvedPicker.java: -------------------------------------------------------------------------------- 1 | package com.zyu; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.LinearGradient; 6 | import android.graphics.Paint; 7 | import android.graphics.Shader; 8 | import android.os.SystemClock; 9 | import android.util.AttributeSet; 10 | 11 | import com.aigestudio.wheelpicker.core.AbstractWheelPicker; 12 | import com.aigestudio.wheelpicker.view.WheelCurvedPicker; 13 | import com.facebook.react.bridge.Arguments; 14 | import com.facebook.react.bridge.ReactContext; 15 | import com.facebook.react.bridge.WritableMap; 16 | import com.facebook.react.uimanager.UIManagerModule; 17 | import com.facebook.react.uimanager.events.Event; 18 | import com.facebook.react.uimanager.events.EventDispatcher; 19 | import com.facebook.react.uimanager.events.RCTEventEmitter; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * @author Sam Yu 25 | */ 26 | public class ReactWheelCurvedPicker extends WheelCurvedPicker { 27 | 28 | private final EventDispatcher mEventDispatcher; 29 | private List mValueData; 30 | 31 | public ReactWheelCurvedPicker(ReactContext reactContext) { 32 | super(reactContext); 33 | mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); 34 | setOnWheelChangeListener(new OnWheelChangeListener() { 35 | @Override 36 | public void onWheelScrolling(float deltaX, float deltaY) { 37 | } 38 | 39 | @Override 40 | public void onWheelSelected(int index, String data) { 41 | if (mValueData != null && index < mValueData.size()) { 42 | mEventDispatcher.dispatchEvent( 43 | new ItemSelectedEvent(getId(), mValueData.get(index))); 44 | } 45 | } 46 | 47 | @Override 48 | public void onWheelScrollStateChanged(int state) { 49 | } 50 | }); 51 | } 52 | 53 | @Override 54 | protected void drawForeground(Canvas canvas) { 55 | super.drawForeground(canvas); 56 | 57 | Paint paint = new Paint(); 58 | paint.setColor(Color.WHITE); 59 | int colorFrom = 0x00FFFFFF;//Color.BLACK; 60 | int colorTo = Color.WHITE; 61 | LinearGradient linearGradientShader = new LinearGradient(rectCurItem.left, rectCurItem.top, rectCurItem.right/2, rectCurItem.top, colorFrom, colorTo, Shader.TileMode.MIRROR); 62 | paint.setShader(linearGradientShader); 63 | canvas.drawLine(rectCurItem.left, rectCurItem.top, rectCurItem.right, rectCurItem.top, paint); 64 | canvas.drawLine(rectCurItem.left, rectCurItem.bottom, rectCurItem.right, rectCurItem.bottom, paint); 65 | } 66 | 67 | @Override 68 | public void setItemIndex(int index) { 69 | super.setItemIndex(index); 70 | unitDeltaTotal = 0; 71 | mHandler.post(this); 72 | } 73 | 74 | public void setValueData(List data) { 75 | mValueData = data; 76 | } 77 | 78 | public int getState() { 79 | return state; 80 | } 81 | } 82 | 83 | class ItemSelectedEvent extends Event { 84 | 85 | public static final String EVENT_NAME = "wheelCurvedPickerPageSelected"; 86 | 87 | private final int mValue; 88 | 89 | protected ItemSelectedEvent(int viewTag, int value) { 90 | super(viewTag); 91 | mValue = value; 92 | } 93 | 94 | @Override 95 | public String getEventName() { 96 | return EVENT_NAME; 97 | } 98 | 99 | @Override 100 | public void dispatch(RCTEventEmitter rctEventEmitter) { 101 | rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); 102 | } 103 | 104 | private WritableMap serializeEventData() { 105 | WritableMap eventData = Arguments.createMap(); 106 | eventData.putInt("data", mValue); 107 | return eventData; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /android/src/main/java/com/zyu/ReactWheelCurvedPickerManager.java: -------------------------------------------------------------------------------- 1 | package com.zyu; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.aigestudio.wheelpicker.core.AbstractWheelPicker; 6 | import com.facebook.react.bridge.ReadableArray; 7 | import com.facebook.react.bridge.ReadableMap; 8 | import com.facebook.react.common.MapBuilder; 9 | import com.facebook.react.uimanager.PixelUtil; 10 | import com.facebook.react.uimanager.SimpleViewManager; 11 | import com.facebook.react.uimanager.ThemedReactContext; 12 | import com.facebook.react.uimanager.annotations.ReactProp; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Map; 16 | 17 | /** 18 | * @author Sam Yu 19 | */ 20 | public class ReactWheelCurvedPickerManager extends SimpleViewManager { 21 | 22 | private static final String REACT_CLASS = "WheelCurvedPicker"; 23 | 24 | private static final int DEFAULT_TEXT_SIZE = 25 * 2; 25 | private static final int DEFAULT_ITEM_SPACE = 14 * 2; 26 | 27 | @Override 28 | protected ReactWheelCurvedPicker createViewInstance(ThemedReactContext reactContext) { 29 | ReactWheelCurvedPicker picker = new ReactWheelCurvedPicker(reactContext); 30 | picker.setTextColor(Color.LTGRAY); 31 | picker.setCurrentTextColor(Color.WHITE); 32 | picker.setTextSize(DEFAULT_TEXT_SIZE); 33 | picker.setItemSpace(DEFAULT_ITEM_SPACE); 34 | 35 | return picker; 36 | } 37 | 38 | @Override 39 | public Map getExportedCustomDirectEventTypeConstants() { 40 | return MapBuilder.of( 41 | ItemSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onValueChange") 42 | ); 43 | } 44 | 45 | @ReactProp(name="data") 46 | public void setData(ReactWheelCurvedPicker picker, ReadableArray items) { 47 | if (picker != null) { 48 | ArrayList valueData = new ArrayList<>(); 49 | ArrayList labelData = new ArrayList<>(); 50 | for (int i = 0; i < items.size(); i ++) { 51 | ReadableMap itemMap = items.getMap(i); 52 | valueData.add(itemMap.getInt("value")); 53 | labelData.add(itemMap.getString("label")); 54 | } 55 | picker.setValueData(valueData); 56 | picker.setData(labelData); 57 | } 58 | } 59 | 60 | @ReactProp(name="selectedIndex") 61 | public void setSelectedIndex(ReactWheelCurvedPicker picker, int index) { 62 | if (picker != null && picker.getState() == AbstractWheelPicker.SCROLL_STATE_IDLE) { 63 | picker.setItemIndex(index); 64 | picker.invalidate(); 65 | } 66 | } 67 | 68 | @ReactProp(name="textColor", customType = "Color") 69 | public void setTextColor(ReactWheelCurvedPicker picker, Integer color) { 70 | if (picker != null) { 71 | picker.setCurrentTextColor(color); 72 | picker.setTextColor(color); 73 | } 74 | } 75 | 76 | @ReactProp(name="textSize") 77 | public void setTextSize(ReactWheelCurvedPicker picker, int size) { 78 | if (picker != null) { 79 | picker.setTextSize((int) PixelUtil.toPixelFromDIP(size)); 80 | } 81 | } 82 | 83 | @ReactProp(name="itemSpace") 84 | public void setItemSpace(ReactWheelCurvedPicker picker, int space) { 85 | if (picker != null) { 86 | picker.setItemSpace((int) PixelUtil.toPixelFromDIP(space)); 87 | } 88 | } 89 | 90 | @Override 91 | public String getName() { 92 | return REACT_CLASS; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lesliesam/react-native-wheel-picker/28c9d7459452d07fb074b874364784a3dbeb8e71/demo.gif -------------------------------------------------------------------------------- /demo_android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lesliesam/react-native-wheel-picker/28c9d7459452d07fb074b874364784a3dbeb8e71/demo_android.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | import { 6 | PickerIOS, 7 | Platform, 8 | } from 'react-native'; 9 | 10 | import WheelCurvedPicker from './WheelCurvedPicker' 11 | 12 | module.exports = (Platform.OS === 'ios' ? PickerIOS : WheelCurvedPicker) 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-wheel-picker", 3 | "version": "1.2.0", 4 | "description": "React native cross platform picker.", 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/lesliesam/react-native-wheel-picker.git" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "picker", 16 | "wheel" 17 | ], 18 | "author": "Yu Zheng", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/lesliesam/react-native-wheel-picker/issues" 22 | }, 23 | "homepage": "https://github.com/lesliesam/react-native-wheel-picker#readme", 24 | "dependencies": {} 25 | } 26 | --------------------------------------------------------------------------------