mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Open sourced spinner aka picker aka drop down for android
Reviewed By: mkonicek Differential Revision: D2830803 fb-gh-sync-id: e6b6fcdbe33d942180cf2c1041076ad71d0473ce
This commit is contained in:
committed by
facebook-github-bot-4
parent
cd89016ee7
commit
18437093f2
@@ -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 (
|
||||
<UIExplorerPage title="<PickerAndroid>">
|
||||
<UIExplorerBlock title="Basic Picker">
|
||||
<PickerAndroid
|
||||
style={{width: 100, height: 56}}
|
||||
onSelect={this.onSelect.bind(this, 'selected1')}>
|
||||
<Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
|
||||
<Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
|
||||
</PickerAndroid>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Disabled picker">
|
||||
<PickerAndroid style={{width: 100, height: 56}} enabled={false}>
|
||||
<Item text="hello" value="key0" selected={this.state.selected1 === 'key0'} />
|
||||
<Item text="world" value="key1" selected={this.state.selected1 === 'key1'} />
|
||||
</PickerAndroid>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Dropdown Picker">
|
||||
<PickerAndroid
|
||||
style={{width: 100, height: 56}}
|
||||
onSelect={this.onSelect.bind(this, 'selected2')}
|
||||
mode="dropdown">
|
||||
<Item text="hello" value="key0" selected={this.state.selected2 === 'key0'} />
|
||||
<Item text="world" value="key1" selected={this.state.selected2 === 'key1'} />
|
||||
</PickerAndroid>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Alternating Picker">
|
||||
<PickerAndroid
|
||||
style={{width: 100, height: 56}}
|
||||
onSelect={this.onSelect.bind(this, 'selected3')}
|
||||
mode={this.state.mode}>
|
||||
<Item text="hello" value="key0" selected={this.state.selected3 === 'key0'} />
|
||||
<Item text="world" value="key1" selected={this.state.selected3 === 'key1'} />
|
||||
</PickerAndroid>
|
||||
<TouchableWithoutFeedback onPress={this.changeMode}>
|
||||
<Text>Tap here to switch between dialog/dropdown.</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Picker with prompt message">
|
||||
<PickerAndroid
|
||||
style={{width: 100, height: 56}}
|
||||
onSelect={this.onSelect.bind(this, 'selected4')}
|
||||
prompt="Pick one, just one">
|
||||
<Item text="hello" value="key0" selected={this.state.selected4 === 'key0'} />
|
||||
<Item text="world" value="key1" selected={this.state.selected4 === 'key1'} />
|
||||
</PickerAndroid>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Picker with no listener">
|
||||
<PickerAndroid style={{width: 100, height: 56}}>
|
||||
<Item text="hello" value="key0" />
|
||||
<Item text="world" value="key1" />
|
||||
</PickerAndroid>
|
||||
<Text>
|
||||
You can not change the value of this picker because it doesn't set a selected prop on
|
||||
its items.
|
||||
</Text>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Colorful pickers">
|
||||
<PickerAndroid style={{width: 100, height: 56, color: 'black'}}
|
||||
onSelect={this.onSelect.bind(this, 'color')}
|
||||
mode="dropdown">
|
||||
<Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
|
||||
<Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
|
||||
<Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'}/>
|
||||
</PickerAndroid>
|
||||
<PickerAndroid style={{width: 100, height: 56}}
|
||||
onSelect={this.onSelect.bind(this, 'color')}
|
||||
mode="dialog">
|
||||
<Item text="red" color="red" value="red" selected={this.state.color === 'red'}/>
|
||||
<Item text="green" color="green" value="green" selected={this.state.color === 'green'}/>
|
||||
<Item text="blue" color="blue" value="blue" selected={this.state.color === 'blue'} />
|
||||
</PickerAndroid>
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
|
||||
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 = '<PickerAndroid>';
|
||||
exports.displayName = 'PickerAndroidExample';
|
||||
exports.description = 'The Android Picker component provides multiple options to choose from';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'PickerAndroidExample',
|
||||
render(): ReactElement { return <PickerAndroidExample />; }
|
||||
},
|
||||
];
|
||||
@@ -29,6 +29,7 @@ var COMPONENTS = [
|
||||
require('./ScrollViewSimpleExample'),
|
||||
require('./SwitchExample'),
|
||||
require('./RefreshControlExample'),
|
||||
require('./PickerAndroidExample'),
|
||||
require('./PullToRefreshViewAndroidExample.android'),
|
||||
require('./TextExample.android'),
|
||||
require('./TextInputExample.android'),
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* <PickerAndroid> - 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:
|
||||
*
|
||||
* <PickerAndroid>
|
||||
* <PickerAndroid.Item text="Java" value="js" />
|
||||
* <PickerAndroid.Item text="JavaScript" value="java" selected={true} />
|
||||
* </PickerAndroid>
|
||||
*/
|
||||
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 <Picker ref={REF_PICKER} {...nativeProps} />;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
+1
@@ -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'); },
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+32
@@ -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);
|
||||
}
|
||||
}
|
||||
+32
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ReactPicker> {
|
||||
|
||||
@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<ReadableMap> {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -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<PickerItemSelectEvent> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user