mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-09 09:12:06 +08: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
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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