/** * 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) { // onItemSelected gets fired immediately after layout because checkSelectionChanged() in // AdapterView updates the selection position from the default INVALID_POSITION. To match iOS // behavior, we don't want the event emitter for onItemSelected to fire right after layout. mSuppressNextEvent = true; 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; } }