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;
+ }
+}