'use strict'; import React, { StyleSheet, PropTypes, View, Text, Animated, Platform, Dimensions, PickerIOS } from 'react-native'; import PickerAndroid from 'react-native-picker-android'; let Picker = Platform.OS === 'ios' ? PickerIOS : PickerAndroid; let PickerItem = Picker.Item; let width = Dimensions.get('window').width; let height = Dimensions.get('window').height; const longSide = width > height ? width : height; const shortSide = width > height ? height : width; export default class PickerAny extends React.Component { static propTypes = { style: PropTypes.any, pickerBtnText: PropTypes.string, pickerCancelBtnText: PropTypes.string, pickerBtnStyle: PropTypes.any, pickerTitle: PropTypes.string, pickerTitleStyle: PropTypes.any, pickerToolBarStyle: PropTypes.any, pickerItemStyle: PropTypes.any, showMask: PropTypes.bool, showDuration: PropTypes.number, pickerData: PropTypes.any.isRequired, selectedValue: PropTypes.any.isRequired, onPickerDone: PropTypes.func, onPickerCancel: PropTypes.func, onValueChange: PropTypes.func }; static defaultProps = { style: { width: width }, pickerBtnText: 'Done', pickerCancelBtnText: 'Cancel', showMask: false, showDuration: 300, onPickerDone: ()=>{}, onPickerCancel: ()=>{}, onValueChange: ()=>{} }; constructor(props, context){ super(props, context); } componentWillMount(){ this.state = this._getStateFromProps(this.props); } componentWillReceiveProps(newProps){ let newState = this._getStateFromProps(newProps); this.setState(newState); } shouldComponentUpdate(nextProps, nextState, context){ return true; } _getStateFromProps(props){ //the pickedValue must looks like [wheelone's, wheeltwo's, ...] //this.state.selectedValue may be the result of the first pickerWheel let { style, pickerBtnText, pickerCancelBtnText, pickerBtnStyle, pickerTitle, pickerTitleStyle, pickerToolBarStyle, pickerItemStyle, showDuration, showMask, pickerData, selectedValue, onPickerDone, onPickerCancel, onValueChange } = props; let pickerStyle = pickerData.constructor === Array ? 'parallel' : 'cascade'; let firstWheelData; let firstPickedData; let secondPickedData; let secondWheelData; let secondPickedDataIndex; let thirdWheelData; let thirdPickedDataIndex; let cascadeData = {}; let slideAnim = (this.state && this.state.slideAnim ? this.state.slideAnim : new Animated.Value(-height)); if(pickerStyle === 'parallel'){ //compatible single wheel sence if(selectedValue.constructor !== Array){ selectedValue = [selectedValue]; } if(pickerData[0].constructor !== Array){ pickerData = [pickerData]; } } else if(pickerStyle === 'cascade'){ //only support three stage firstWheelData = Object.keys(pickerData); firstPickedData = props.selectedValue[0]; secondPickedData = props.selectedValue[1]; cascadeData = this._getCascadeData(pickerData, selectedValue, firstPickedData, secondPickedData, true); } //save picked data this.pickedValue = JSON.parse(JSON.stringify(selectedValue)); this.pickerStyle = pickerStyle; return { style, pickerBtnText, pickerCancelBtnText, pickerBtnStyle, pickerTitle, pickerTitleStyle, pickerToolBarStyle, pickerItemStyle, showDuration, showMask, pickerData, selectedValue, onPickerDone, onPickerCancel, onValueChange, //list of first wheel data firstWheelData, //first wheel selected value firstPickedData, slideAnim, //list of second wheel data and pickedDataIndex secondWheelData: cascadeData.secondWheelData, secondPickedDataIndex: cascadeData.secondPickedDataIndex, //third wheel selected value and pickedDataIndex thirdWheelData: cascadeData.thirdWheelData, thirdPickedDataIndex: cascadeData.thirdPickedDataIndex }; } _slideUp(){ this._isMoving = true; Animated.timing( this.state.slideAnim, { toValue: 0, duration: this.state.showDuration, } ).start((evt) => { if(evt.finished) { this._isMoving = false; this._isPickerShow = true; } }); } _slideDown(){ this._isMoving = true; Animated.timing( this.state.slideAnim, { toValue: -height, duration: this.state.showDuration, } ).start((evt) => { if(evt.finished) { this._isMoving = false; this._isPickerShow = false; } }); } _toggle(){ if(this._isMoving) { return; } if(this._isPickerShow) { this._slideDown(); } else{ this._slideUp(); } } //向父组件提供方法 toggle(){ this._toggle(); } show(){ if(!this._isPickerShow){ this._slideUp(); } } hide(){ if(this._isPickerShow){ this._slideDown(); } } isPickerShow(){ return this._isPickerShow; } _prePressHandle(callback){ //通知子组件往上滚 this.pickerWheel.moveUp(); } _nextPressHandle(callback){ //通知子组件往下滚 this.pickerWheel.moveDown(); } _pickerCancel(){ this._toggle(); this.state.onPickerCancel(); } _pickerFinish(){ this._toggle(); this.state.onPickerDone(this.pickedValue); } _renderParallelWheel(pickerData){ return pickerData.map((item, index) => { return ( { this.pickedValue.splice(index, 1, value); //do not set state to another object!! why? // this.setState({ // selectedValue: this.pickedValue // }); this.setState({ selectedValue: JSON.parse(JSON.stringify(this.pickedValue)) }); this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), index); }} > {item.map((value, index) => ( ) )} ); }); } _getCascadeData(pickerData, pickedValue, firstPickedData, secondPickedData, onInit){ let secondWheelData; let secondPickedDataIndex; let thirdWheelData; let thirdPickedDataIndex; //only support two and three stage for(let key in pickerData){ //two stage if(pickerData[key].constructor === Array){ secondWheelData = pickerData[firstPickedData]; if(onInit){ secondWheelData.forEach(function(v, k){ if(v === pickedValue[1]){ secondPickedDataIndex = k; } }.bind(this)); } else{ secondPickedDataIndex = 0; } break; } //three stage else{ secondWheelData = Object.keys(pickerData[firstPickedData]); if(onInit){ secondWheelData.forEach(function(v, k){ if(v === pickedValue[1]){ secondPickedDataIndex = k; } }.bind(this)); } else{ secondPickedDataIndex = 0; } thirdWheelData = pickerData[firstPickedData][secondPickedData]; if(onInit){ thirdWheelData.forEach(function(v, k){ if(v === pickedValue[2]){ thirdPickedDataIndex = k; } }) } else{ thirdPickedDataIndex = 0; } break; } } return { secondWheelData, secondPickedDataIndex, thirdWheelData, thirdPickedDataIndex } } _renderCascadeWheel(pickerData){ let thirdWheel = this.state.thirdWheelData && ( { this.pickedValue.splice(2, 1, this.state.thirdWheelData[index]); this.setState({ thirdPickedDataIndex: index, selectedValue: 'wheel3'+index }); this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 2); }} > {this.state.thirdWheelData.map((value, index) => ( ) )} ); return ( { let secondWheelData = Object.keys(pickerData[value]); let cascadeData = this._getCascadeData(pickerData, this.pickedValue, value, secondWheelData[0]); //when onPicked, this.pickedValue will pass to the parent //when firstWheel changed, second and third will also change if(cascadeData.thirdWheelData){ this.pickedValue.splice(0, 3, value, cascadeData.secondWheelData[0], cascadeData.thirdWheelData[0]); } else{ this.pickedValue.splice(0, 2, value, cascadeData.secondWheelData[0]); } this.setState({ selectedValue: 'wheel1'+value, firstPickedData: value, secondWheelData: cascadeData.secondWheelData, secondPickedDataIndex: 0, thirdWheelData: cascadeData.thirdWheelData, thirdPickedDataIndex: 0 }); this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 0); this.refs.secondWheel && this.refs.secondWheel.moveTo && this.refs.secondWheel.moveTo(0); this.refs.thirdWheel && this.refs.thirdWheel.moveTo && this.refs.thirdWheel.moveTo(0); }} > {this.state.firstWheelData.map((value, index) => ( ) )} { let thirdWheelData = pickerData[this.state.firstPickedData][this.state.secondWheelData[index]]; if(thirdWheelData){ this.pickedValue.splice(1, 2, this.state.secondWheelData[index], thirdWheelData[0]); } else{ this.pickedValue.splice(1, 1, this.state.secondWheelData[index]); } this.setState({ secondPickedDataIndex: index, thirdWheelData, thirdPickedDataIndex: 0, selectedValue: 'wheel2'+index }); this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 1); this.refs.thirdWheel && this.refs.thirdWheel.moveTo && this.refs.thirdWheel.moveTo(0); }} > {this.state.secondWheelData.map((value, index) => ( ) )} {thirdWheel} ); } _renderWheel(pickerData){ let wheel = null; if(this.pickerStyle === 'parallel'){ wheel = this._renderParallelWheel(pickerData); } else if(this.pickerStyle === 'cascade'){ wheel = this._renderCascadeWheel(pickerData); } return wheel; } render(){ let mask = this.state.showMask ? ( ) : null; return ( {mask} {this.state.pickerCancelBtnText} {this.state.pickerTitle} {this.state.pickerBtnText} {this._renderWheel(this.state.pickerData)} ); } }; let styles = StyleSheet.create({ picker: { position: 'absolute', bottom: 0, left: 0, backgroundColor: 'transparent', overflow: 'hidden' }, pickerBox: { position: 'absolute', bottom: 0, left: 0 }, mask: { position: 'absolute', top: 0, backgroundColor: 'transparent', height: height, width: width }, pickerWrap: { flexDirection: 'row' }, pickerWheel: { flex: 1 }, pickerToolbar: { height: 30, backgroundColor: '#e6e6e6', flexDirection: 'row', borderTopWidth: 1, borderBottomWidth: 1, borderColor: '#c3c3c3', alignItems: 'center' }, pickerBtnView: { flex: 1, flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' }, pickerMoveBtn: { color: '#149be0', fontSize: 16, marginLeft: 20 }, pickerCancelBtn: { flex: 1, flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', marginLeft: 20 }, pickerTitle: { flex: 4, color: 'black', textAlign: 'center' }, pickerFinishBtn: { flex: 1, flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', marginRight: 20 }, pickerFinishBtnText: { fontSize: 16, color: '#149be0' } });