From 18437093f2664beedd3239bb525d7daa3f2bcdbd Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 15 Jan 2016 06:23:15 -0800 Subject: [PATCH] Open sourced spinner aka picker aka drop down for android Reviewed By: mkonicek Differential Revision: D2830803 fb-gh-sync-id: e6b6fcdbe33d942180cf2c1041076ad71d0473ce --- Examples/UIExplorer/PickerAndroidExample.js | 142 ++++++++++++ Examples/UIExplorer/UIExplorerList.android.js | 1 + .../Components/PickerAndroid/PickerAndroid.js | 213 ++++++++++++++++++ Libraries/react-native/react-native.js | 1 + Libraries/react-native/react-native.js.flow | 1 + .../react/shell/MainReactPackage.java | 4 + .../picker/ReactDialogPickerManager.java | 32 +++ .../picker/ReactDropdownPickerManager.java | 32 +++ .../react/views/picker/ReactPicker.java | 148 ++++++++++++ .../views/picker/ReactPickerManager.java | 163 ++++++++++++++ .../picker/events/PickerItemSelectEvent.java | 42 ++++ 11 files changed, 779 insertions(+) create mode 100644 Examples/UIExplorer/PickerAndroidExample.js create mode 100644 Libraries/Components/PickerAndroid/PickerAndroid.js create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js new file mode 100644 index 000000000..91a2bd5a4 --- /dev/null +++ b/Examples/UIExplorer/PickerAndroidExample.js @@ -0,0 +1,142 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const React = require('react-native'); +const UIExplorerBlock = require('UIExplorerBlock'); +const UIExplorerPage = require('UIExplorerPage'); + +const { + PickerAndroid, + Text, + TouchableWithoutFeedback, +} = React; +const Item = PickerAndroid.Item; + +const PickerAndroidExample = React.createClass({ + getInitialState: function() { + return { + selected1: 'key1', + selected2: 'key1', + selected3: 'key1', + selected4: 'key1', + color: 'red', + mode: PickerAndroid.MODE_DIALOG, + }; + }, + + displayName: 'Android Picker', + + render: function() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + Tap here to switch between dialog/dropdown. + + + + + + + + + + + + + + + You can not change the value of this picker because it doesn't set a selected prop on + its items. + + + + + + + + + + + + + + + + ); + }, + + changeMode: function() { + const newMode = this.state.mode === PickerAndroid.MODE_DIALOG + ? PickerAndroid.MODE_DROPDOWN + : PickerAndroid.MODE_DIALOG; + this.setState({mode: newMode}); + }, + + onSelect: function(key, value) { + const newState = {}; + newState[key] = value; + this.setState(newState); + }, +}); + +exports.title = ''; +exports.displayName = 'PickerAndroidExample'; +exports.description = 'The Android Picker component provides multiple options to choose from'; +exports.examples = [ + { + title: 'PickerAndroidExample', + render(): ReactElement { return ; } + }, +]; diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 1976128b1..7d56b94f8 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -29,6 +29,7 @@ var COMPONENTS = [ require('./ScrollViewSimpleExample'), require('./SwitchExample'), require('./RefreshControlExample'), + require('./PickerAndroidExample'), require('./PullToRefreshViewAndroidExample.android'), require('./TextExample.android'), require('./TextInputExample.android'), diff --git a/Libraries/Components/PickerAndroid/PickerAndroid.js b/Libraries/Components/PickerAndroid/PickerAndroid.js new file mode 100644 index 000000000..1ec105ca4 --- /dev/null +++ b/Libraries/Components/PickerAndroid/PickerAndroid.js @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PickerAndroid + * @flow + */ + +'use strict'; + +var ColorPropType = require('ColorPropType'); +var React = require('React'); +var ReactChildren = require('ReactChildren'); +var ReactPropTypes = require('ReactPropTypes'); +var StyleSheetPropType = require('StyleSheetPropType'); +var View = require('View'); +var ViewStylePropTypes = require('ViewStylePropTypes'); + +var processColor = require('processColor'); +var requireNativeComponent = require('requireNativeComponent'); + +var MODE_DIALOG = 'dialog'; +var MODE_DROPDOWN = 'dropdown'; +var REF_PICKER = 'picker'; + +var pickerStyleType = StyleSheetPropType({ + ...ViewStylePropTypes, + color: ColorPropType, +}); + +type Items = { + selected: number; + items: any[]; +}; + +type Event = Object; + +/** + * Individual selectable item in a Picker. + */ +var Item = React.createClass({ + + propTypes: { + /** + * Color of this item's text. + */ + color: ColorPropType, + /** + * Text to display for this item. + */ + text: ReactPropTypes.string.isRequired, + /** + * The value to be passed to picker's `onSelect` callback when this item is selected. + */ + value: ReactPropTypes.string, + /** + * If `true`, this item is selected and shown in the picker. + * Usually this is set based on state. + */ + selected: ReactPropTypes.bool, + /** + * Used to locate this view in end-to-end tests. + */ + testID: ReactPropTypes.string, + }, + + render: function() { + throw new Error('Picker items should never be rendered'); + }, + +}); + +/** + * - A React component that renders the native Picker widget on Android. The items + * that can be selected are specified as children views of type Item. Example usage: + * + * + * + * + * + */ +var PickerAndroid = React.createClass({ + + propTypes: { + ...View.propTypes, + style: pickerStyleType, + /** + * If set to false, the picker will be disabled, i.e. the user will not be able to make a + * selection. + */ + enabled: ReactPropTypes.bool, + /** + * Specifies how to display the selection items when the user taps on the picker: + * + * - dialog: Show a modal dialog + * - dropdown: Shows a dropdown anchored to the picker view + */ + mode: ReactPropTypes.oneOf([MODE_DIALOG, MODE_DROPDOWN]), + /** + * Callback for when an item is selected. This is called with the following parameters: + * + * - `itemValue`: the `value` prop of the item that was selected + * - `itemPosition`: the index of the selected item in this picker + */ + onSelect: ReactPropTypes.func, + /** + * Prompt string for this picker, currently only used in `dialog` mode as the title of the + * dialog. + */ + prompt: ReactPropTypes.string, + /** + * Used to locate this view in end-to-end tests. + */ + testID: ReactPropTypes.string, + }, + + statics: { + Item: Item, + MODE_DIALOG: MODE_DIALOG, + MODE_DROPDOWN: MODE_DROPDOWN, + }, + + getDefaultProps: function() { + return { + mode: MODE_DIALOG, + }; + }, + + render: function() { + var Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker; + + var { selected, items } = this._getItems(); + + var nativeProps = { + enabled: this.props.enabled, + items: items, + mode: this.props.mode, + onSelect: this._onSelect, + prompt: this.props.prompt, + selected: selected, + style: this.props.style, + testID: this.props.testID, + }; + + return ; + }, + + /** + * Transform this view's children into an array of items to be passed to the native component. + * Since we're traversing the children, also determine the selected position. + * + * @returns an object with two keys: + * + * - `selected` (number) - the index of the selected item + * - `items` (array) - the items of this picker, as an array of strings + */ + _getItems: function(): Items { + var items = []; + var selected = 0; + ReactChildren.forEach(this.props.children, function(child, index) { + var childProps = Object.assign({}, child.props); + if (childProps.color) { + childProps.color = processColor(childProps.color); + } + items.push(childProps); + if (childProps.selected) { + selected = index; + } + }); + return { + selected: selected, + items: items, + }; + }, + + _onSelect: function(event: Event) { + if (this.props.onSelect) { + var position = event.nativeEvent.position; + if (position >= 0) { + var value = this.props.children[position].props.value; + this.props.onSelect(value, position); + } else { + this.props.onSelect(null, position); + } + } + + // The native Picker has changed, but the props haven't (yet). If + // the handler decides to not accept the new value or do something + // else with it we might end up in a bad state, so we reset the + // selection on the native component. + // tl;dr: PickerAndroid is a controlled component. + var { selected } = this._getItems(); + if (this.refs[REF_PICKER]) { + this.refs[REF_PICKER].setNativeProps({selected: selected}); + } + }, + +}); + +var cfg = { + nativeOnly: { + items: true, + selected: true, + } +} +var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg); +var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg); + +module.exports = PickerAndroid; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index b28f255d9..16ca8ad82 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -25,6 +25,7 @@ var ReactNative = { get Modal() { return require('Modal'); }, get Navigator() { return require('Navigator'); }, get NavigatorIOS() { return require('NavigatorIOS'); }, + get PickerAndroid() { return require('PickerAndroid'); }, get PickerIOS() { return require('PickerIOS'); }, get ProgressBarAndroid() { return require('ProgressBarAndroid'); }, get ProgressViewIOS() { return require('ProgressViewIOS'); }, diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow index a178f69cf..2eb844c4d 100644 --- a/Libraries/react-native/react-native.js.flow +++ b/Libraries/react-native/react-native.js.flow @@ -37,6 +37,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { Modal: require('Modal'), Navigator: require('Navigator'), NavigatorIOS: require('NavigatorIOS'), + PickerAndroid: require('PickerAndroid'), PickerIOS: require('PickerIOS'), ProgressBarAndroid: require('ProgressBarAndroid'), ProgressViewIOS: require('ProgressViewIOS'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 6d98ea60a..5f0d2d4ea 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -32,6 +32,8 @@ import com.facebook.react.views.art.ARTRenderableViewManager; import com.facebook.react.views.art.ARTSurfaceViewManager; import com.facebook.react.views.drawer.ReactDrawerLayoutManager; import com.facebook.react.views.image.ReactImageManager; +import com.facebook.react.views.picker.ReactDialogPickerManager; +import com.facebook.react.views.picker.ReactDropdownPickerManager; import com.facebook.react.views.progressbar.ReactProgressBarViewManager; import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager; import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager; @@ -82,7 +84,9 @@ public class MainReactPackage implements ReactPackage { ARTRenderableViewManager.createARTShapeViewManager(), ARTRenderableViewManager.createARTTextViewManager(), new ARTSurfaceViewManager(), + new ReactDialogPickerManager(), new ReactDrawerLayoutManager(), + new ReactDropdownPickerManager(), new ReactHorizontalScrollViewManager(), new ReactImageManager(), new ReactProgressBarViewManager(), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java new file mode 100644 index 000000000..c86709467 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.picker; + +import android.widget.Spinner; + +import com.facebook.react.uimanager.ThemedReactContext; + +/** + * {@link ReactPickerManager} for {@link ReactPicker} with {@link Spinner#MODE_DIALOG}. + */ +public class ReactDialogPickerManager extends ReactPickerManager { + + private static final String REACT_CLASS = "AndroidDialogPicker"; + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected ReactPicker createViewInstance(ThemedReactContext reactContext) { + return new ReactPicker(reactContext, Spinner.MODE_DIALOG); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java new file mode 100644 index 000000000..c8be56002 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.picker; + +import android.widget.Spinner; + +import com.facebook.react.uimanager.ThemedReactContext; + +/** + * {@link ReactPickerManager} for {@link ReactPicker} with {@link Spinner#MODE_DROPDOWN}. + */ +public class ReactDropdownPickerManager extends ReactPickerManager { + + private static final String REACT_CLASS = "AndroidDropdownPicker"; + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected ReactPicker createViewInstance(ThemedReactContext reactContext) { + return new ReactPicker(reactContext, Spinner.MODE_DROPDOWN); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java new file mode 100644 index 000000000..2871cd212 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.picker; + +import javax.annotation.Nullable; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; + +import com.facebook.react.common.annotations.VisibleForTesting; + +public class ReactPicker extends Spinner { + + private int mMode = MODE_DIALOG; + private @Nullable Integer mPrimaryColor; + private boolean mSuppressNextEvent; + private @Nullable OnSelectListener mOnSelectListener; + private @Nullable Integer mStagedSelection; + + /** + * Listener interface for ReactPicker events. + */ + public interface OnSelectListener { + void onItemSelected(int position); + } + + public ReactPicker(Context context) { + super(context); + } + + public ReactPicker(Context context, int mode) { + super(context, mode); + mMode = mode; + } + + public ReactPicker(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ReactPicker(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public ReactPicker(Context context, AttributeSet attrs, int defStyle, int mode) { + super(context, attrs, defStyle, mode); + mMode = mode; + } + + private final Runnable measureAndLayout = new Runnable() { + @Override + public void run() { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + + @Override + public void requestLayout() { + super.requestLayout(); + + // The spinner relies on a measure + layout pass happening after it calls requestLayout(). + // Without this, the widget never actually changes the selection and doesn't call the + // appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never + // happens after a call to requestLayout, so we simulate one here. + post(measureAndLayout); + } + + public void setOnSelectListener(@Nullable OnSelectListener onSelectListener) { + if (getOnItemSelectedListener() == null) { + setOnItemSelectedListener( + new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (!mSuppressNextEvent && mOnSelectListener != null) { + mOnSelectListener.onItemSelected(position); + } + mSuppressNextEvent = false; + } + + @Override + public void onNothingSelected(AdapterView parent) { + if (!mSuppressNextEvent && mOnSelectListener != null) { + mOnSelectListener.onItemSelected(-1); + } + mSuppressNextEvent = false; + } + }); + } + mOnSelectListener = onSelectListener; + } + + @Nullable public OnSelectListener getOnSelectListener() { + return mOnSelectListener; + } + + /** + * Will cache "selection" value locally and set it only once {@link #updateStagedSelection} is + * called + */ + public void setStagedSelection(int selection) { + mStagedSelection = selection; + } + + public void updateStagedSelection() { + if (mStagedSelection != null) { + setSelectionWithSuppressEvent(mStagedSelection); + mStagedSelection = null; + } + } + + /** + * Set the selection while suppressing the follow-up {@link OnSelectListener#onItemSelected(int)} + * event. This is used so we don't get an event when changing the selection ourselves. + * + * @param position the position of the selected item + */ + private void setSelectionWithSuppressEvent(int position) { + if (position != getSelectedItemPosition()) { + mSuppressNextEvent = true; + setSelection(position); + } + } + + public @Nullable Integer getPrimaryColor() { + return mPrimaryColor; + } + + public void setPrimaryColor(@Nullable Integer primaryColor) { + mPrimaryColor = primaryColor; + } + + @VisibleForTesting + public int getMode() { + return mMode; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java new file mode 100644 index 000000000..fe2f23cd0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.picker; + +import javax.annotation.Nullable; + +import android.content.Context; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.TextView; + +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewProps; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.views.picker.events.PickerItemSelectEvent; + +/** + * {@link ViewManager} for the {@link ReactPicker} view. This is abstract because the + * {@link Spinner} doesn't support setting the mode (dropdown/dialog) outside the constructor, so + * that is delegated to the separate {@link ReactDropdownPickerManager} and + * {@link ReactDialogPickerManager} components. These are merged back on the JS side into one + * React component. + */ +public abstract class ReactPickerManager extends SimpleViewManager { + + @ReactProp(name = "items") + public void setItems(ReactPicker view, @Nullable ReadableArray items) { + if (items != null) { + ReadableMap[] data = new ReadableMap[items.size()]; + for (int i = 0; i < items.size(); i++) { + data[i] = items.getMap(i); + } + ReactPickerAdapter adapter = new ReactPickerAdapter(view.getContext(), data); + adapter.setPrimaryTextColor(view.getPrimaryColor()); + view.setAdapter(adapter); + } else { + view.setAdapter(null); + } + } + + @ReactProp(name = ViewProps.COLOR, customType = "Color") + public void setColor(ReactPicker view, @Nullable Integer color) { + view.setPrimaryColor(color); + ReactPickerAdapter adapter = (ReactPickerAdapter) view.getAdapter(); + if (adapter != null) { + adapter.setPrimaryTextColor(color); + } + } + + @ReactProp(name = "prompt") + public void setPrompt(ReactPicker view, @Nullable String prompt) { + view.setPrompt(prompt); + } + + @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) + public void setEnabled(ReactPicker view, boolean enabled) { + view.setEnabled(enabled); + } + + @ReactProp(name = "selected") + public void setSelected(ReactPicker view, int selected) { + view.setStagedSelection(selected); + } + + @Override + protected void onAfterUpdateTransaction(ReactPicker view) { + super.onAfterUpdateTransaction(view); + view.updateStagedSelection(); + } + + @Override + protected void addEventEmitters( + final ThemedReactContext reactContext, + final ReactPicker picker) { + picker.setOnSelectListener( + new PickerEventEmitter( + picker, + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher())); + } + + private static class ReactPickerAdapter extends ArrayAdapter { + + private final LayoutInflater mInflater; + private @Nullable Integer mPrimaryTextColor; + + public ReactPickerAdapter(Context context, ReadableMap[] data) { + super(context, 0, data); + + mInflater = (LayoutInflater) Assertions.assertNotNull( + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return getView(position, convertView, parent, false); + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return getView(position, convertView, parent, true); + } + + private View getView(int position, View convertView, ViewGroup parent, boolean isDropdown) { + ReadableMap item = getItem(position); + + if (convertView == null) { + int layoutResId = isDropdown + ? android.R.layout.simple_spinner_dropdown_item + : android.R.layout.simple_spinner_item; + convertView = mInflater.inflate(layoutResId, parent, false); + } + + TextView textView = (TextView) convertView; + textView.setText(item.getString("text")); + if (!isDropdown && mPrimaryTextColor != null) { + textView.setTextColor(mPrimaryTextColor); + } else if (item.hasKey("color") && !item.isNull("color")) { + textView.setTextColor(item.getInt("color")); + } + + return convertView; + } + + public void setPrimaryTextColor(@Nullable Integer primaryTextColor) { + mPrimaryTextColor = primaryTextColor; + notifyDataSetChanged(); + } + } + + private static class PickerEventEmitter implements ReactPicker.OnSelectListener { + + private final ReactPicker mReactPicker; + private final EventDispatcher mEventDispatcher; + + public PickerEventEmitter(ReactPicker reactPicker, EventDispatcher eventDispatcher) { + mReactPicker = reactPicker; + mEventDispatcher = eventDispatcher; + } + + @Override + public void onItemSelected(int position) { + mEventDispatcher.dispatchEvent( new PickerItemSelectEvent( + mReactPicker.getId(), SystemClock.uptimeMillis(), position)); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java new file mode 100644 index 000000000..cabe53fdc --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.picker.events; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +public class PickerItemSelectEvent extends Event { + public static final String EVENT_NAME = "topSelect"; + + private final int mPosition; + + public PickerItemSelectEvent(int id, long uptimeMillis, int position) { + super(id, uptimeMillis); + mPosition = position; + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData()); + } + + private WritableMap serializeEventData() { + WritableMap eventData = Arguments.createMap(); + eventData.putInt("position", mPosition); + return eventData; + } +}