Spinner.java revision 95148495fc3d33e4ad4dcbdee83317422db94dbf
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.annotation.Widget;
20import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnClickListener;
24import android.content.res.TypedArray;
25import android.database.DataSetObserver;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.util.AttributeSet;
29import android.util.DisplayMetrics;
30import android.view.Gravity;
31import android.view.View;
32import android.view.ViewGroup;
33
34
35/**
36 * A view that displays one child at a time and lets the user pick among them.
37 * The items in the Spinner come from the {@link Adapter} associated with
38 * this view.
39 *
40 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
41 * tutorial</a>.</p>
42 *
43 * @attr ref android.R.styleable#Spinner_prompt
44 */
45@Widget
46public class Spinner extends AbsSpinner implements OnClickListener {
47    private static final String TAG = "Spinner";
48
49    // Only measure this many items to get a decent max width.
50    private static final int MAX_ITEMS_MEASURED = 15;
51
52    /**
53     * Use a dialog window for selecting spinner options.
54     */
55    public static final int MODE_DIALOG = 0;
56
57    /**
58     * Use a dropdown anchored to the Spinner for selecting spinner options.
59     */
60    public static final int MODE_DROPDOWN = 1;
61
62    /**
63     * Use the theme-supplied value to select the dropdown mode.
64     */
65    private static final int MODE_THEME = -1;
66
67    private SpinnerPopup mPopup;
68    private DropDownAdapter mTempAdapter;
69    int mDropDownWidth;
70
71    private int mGravity;
72    private boolean mDisableChildrenWhenDisabled;
73
74    private Rect mTempRect = new Rect();
75
76    /**
77     * Construct a new spinner with the given context's theme.
78     *
79     * @param context The Context the view is running in, through which it can
80     *        access the current theme, resources, etc.
81     */
82    public Spinner(Context context) {
83        this(context, null);
84    }
85
86    /**
87     * Construct a new spinner with the given context's theme and the supplied
88     * mode of displaying choices. <code>mode</code> may be one of
89     * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
90     *
91     * @param context The Context the view is running in, through which it can
92     *        access the current theme, resources, etc.
93     * @param mode Constant describing how the user will select choices from the spinner.
94     *
95     * @see #MODE_DIALOG
96     * @see #MODE_DROPDOWN
97     */
98    public Spinner(Context context, int mode) {
99        this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
100    }
101
102    /**
103     * Construct a new spinner with the given context's theme and the supplied attribute set.
104     *
105     * @param context The Context the view is running in, through which it can
106     *        access the current theme, resources, etc.
107     * @param attrs The attributes of the XML tag that is inflating the view.
108     */
109    public Spinner(Context context, AttributeSet attrs) {
110        this(context, attrs, com.android.internal.R.attr.spinnerStyle);
111    }
112
113    /**
114     * Construct a new spinner with the given context's theme, the supplied attribute set,
115     * and default style.
116     *
117     * @param context The Context the view is running in, through which it can
118     *        access the current theme, resources, etc.
119     * @param attrs The attributes of the XML tag that is inflating the view.
120     * @param defStyle The default style to apply to this view. If 0, no style
121     *        will be applied (beyond what is included in the theme). This may
122     *        either be an attribute resource, whose value will be retrieved
123     *        from the current theme, or an explicit style resource.
124     */
125    public Spinner(Context context, AttributeSet attrs, int defStyle) {
126        this(context, attrs, defStyle, MODE_THEME);
127    }
128
129    /**
130     * Construct a new spinner with the given context's theme, the supplied attribute set,
131     * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
132     * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
133     *
134     * @param context The Context the view is running in, through which it can
135     *        access the current theme, resources, etc.
136     * @param attrs The attributes of the XML tag that is inflating the view.
137     * @param defStyle The default style to apply to this view. If 0, no style
138     *        will be applied (beyond what is included in the theme). This may
139     *        either be an attribute resource, whose value will be retrieved
140     *        from the current theme, or an explicit style resource.
141     * @param mode Constant describing how the user will select choices from the spinner.
142     *
143     * @see #MODE_DIALOG
144     * @see #MODE_DROPDOWN
145     */
146    public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
147        super(context, attrs, defStyle);
148
149        TypedArray a = context.obtainStyledAttributes(attrs,
150                com.android.internal.R.styleable.Spinner, defStyle, 0);
151
152        if (mode == MODE_THEME) {
153            mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
154        }
155
156        switch (mode) {
157        case MODE_DIALOG: {
158            mPopup = new DialogPopup();
159            break;
160        }
161
162        case MODE_DROPDOWN: {
163            DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
164
165            mDropDownWidth = a.getLayoutDimension(
166                    com.android.internal.R.styleable.Spinner_dropDownWidth,
167                    ViewGroup.LayoutParams.WRAP_CONTENT);
168            popup.setBackgroundDrawable(a.getDrawable(
169                    com.android.internal.R.styleable.Spinner_popupBackground));
170            final int verticalOffset = a.getDimensionPixelOffset(
171                    com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0);
172            if (verticalOffset != 0) {
173                popup.setVerticalOffset(verticalOffset);
174            }
175
176            final int horizontalOffset = a.getDimensionPixelOffset(
177                    com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0);
178            if (horizontalOffset != 0) {
179                popup.setHorizontalOffset(horizontalOffset);
180            }
181
182            mPopup = popup;
183            break;
184        }
185        }
186
187        mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
188
189        mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
190
191        mDisableChildrenWhenDisabled = a.getBoolean(
192                com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
193
194        a.recycle();
195
196        // Base constructor can call setAdapter before we initialize mPopup.
197        // Finish setting things up if this happened.
198        if (mTempAdapter != null) {
199            mPopup.setAdapter(mTempAdapter);
200            mTempAdapter = null;
201        }
202    }
203
204    @Override
205    public void setEnabled(boolean enabled) {
206        super.setEnabled(enabled);
207        if (mDisableChildrenWhenDisabled) {
208            final int count = getChildCount();
209            for (int i = 0; i < count; i++) {
210                getChildAt(i).setEnabled(enabled);
211            }
212        }
213    }
214
215    /**
216     * Describes how the selected item view is positioned. Currently only the horizontal component
217     * is used. The default is determined by the current theme.
218     *
219     * @param gravity See {@link android.view.Gravity}
220     *
221     * @attr ref android.R.styleable#Spinner_gravity
222     */
223    public void setGravity(int gravity) {
224        if (mGravity != gravity) {
225            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
226                gravity |= Gravity.LEFT;
227            }
228            mGravity = gravity;
229            requestLayout();
230        }
231    }
232
233    @Override
234    public void setAdapter(SpinnerAdapter adapter) {
235        super.setAdapter(adapter);
236
237        if (mPopup != null) {
238            mPopup.setAdapter(new DropDownAdapter(adapter));
239        } else {
240            mTempAdapter = new DropDownAdapter(adapter);
241        }
242    }
243
244    @Override
245    public int getBaseline() {
246        View child = null;
247
248        if (getChildCount() > 0) {
249            child = getChildAt(0);
250        } else if (mAdapter != null && mAdapter.getCount() > 0) {
251            child = makeAndAddView(0);
252            mRecycler.put(0, child);
253            removeAllViewsInLayout();
254        }
255
256        if (child != null) {
257            final int childBaseline = child.getBaseline();
258            return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
259        } else {
260            return -1;
261        }
262    }
263
264    @Override
265    protected void onDetachedFromWindow() {
266        super.onDetachedFromWindow();
267
268        if (mPopup != null && mPopup.isShowing()) {
269            mPopup.dismiss();
270        }
271    }
272
273    /**
274     * <p>A spinner does not support item click events. Calling this method
275     * will raise an exception.</p>
276     *
277     * @param l this listener will be ignored
278     */
279    @Override
280    public void setOnItemClickListener(OnItemClickListener l) {
281        throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
282    }
283
284    @Override
285    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
286        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
287        if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
288            final int measuredWidth = getMeasuredWidth();
289            setMeasuredDimension(Math.min(Math.max(measuredWidth,
290                    measureContentWidth(getAdapter(), getBackground())),
291                    MeasureSpec.getSize(widthMeasureSpec)),
292                    getMeasuredHeight());
293        }
294    }
295
296    /**
297     * @see android.view.View#onLayout(boolean,int,int,int,int)
298     *
299     * Creates and positions all views
300     *
301     */
302    @Override
303    protected void onLayout(boolean changed, int l, int t, int r, int b) {
304        super.onLayout(changed, l, t, r, b);
305        mInLayout = true;
306        layout(0, false);
307        mInLayout = false;
308    }
309
310    /**
311     * Creates and positions all views for this Spinner.
312     *
313     * @param delta Change in the selected position. +1 moves selection is moving to the right,
314     * so views are scrolling to the left. -1 means selection is moving to the left.
315     */
316    @Override
317    void layout(int delta, boolean animate) {
318        int childrenLeft = mSpinnerPadding.left;
319        int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
320
321        if (mDataChanged) {
322            handleDataChanged();
323        }
324
325        // Handle the empty set by removing all views
326        if (mItemCount == 0) {
327            resetList();
328            return;
329        }
330
331        if (mNextSelectedPosition >= 0) {
332            setSelectedPositionInt(mNextSelectedPosition);
333        }
334
335        recycleAllViews();
336
337        // Clear out old views
338        removeAllViewsInLayout();
339
340        // Make selected view and position it
341        mFirstPosition = mSelectedPosition;
342        View sel = makeAndAddView(mSelectedPosition);
343        int width = sel.getMeasuredWidth();
344        int selectedOffset = childrenLeft;
345        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
346            case Gravity.CENTER_HORIZONTAL:
347                selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
348                break;
349            case Gravity.RIGHT:
350                selectedOffset = childrenLeft + childrenWidth - width;
351                break;
352        }
353        sel.offsetLeftAndRight(selectedOffset);
354
355        // Flush any cached views that did not get reused above
356        mRecycler.clear();
357
358        invalidate();
359
360        checkSelectionChanged();
361
362        mDataChanged = false;
363        mNeedSync = false;
364        setNextSelectedPositionInt(mSelectedPosition);
365    }
366
367    /**
368     * Obtain a view, either by pulling an existing view from the recycler or
369     * by getting a new one from the adapter. If we are animating, make sure
370     * there is enough information in the view's layout parameters to animate
371     * from the old to new positions.
372     *
373     * @param position Position in the spinner for the view to obtain
374     * @return A view that has been added to the spinner
375     */
376    private View makeAndAddView(int position) {
377
378        View child;
379
380        if (!mDataChanged) {
381            child = mRecycler.get(position);
382            if (child != null) {
383                // Position the view
384                setUpChild(child);
385
386                return child;
387            }
388        }
389
390        // Nothing found in the recycler -- ask the adapter for a view
391        child = mAdapter.getView(position, null, this);
392
393        // Position the view
394        setUpChild(child);
395
396        return child;
397    }
398
399    /**
400     * Helper for makeAndAddView to set the position of a view
401     * and fill out its layout paramters.
402     *
403     * @param child The view to position
404     */
405    private void setUpChild(View child) {
406
407        // Respect layout params that are already in the view. Otherwise
408        // make some up...
409        ViewGroup.LayoutParams lp = child.getLayoutParams();
410        if (lp == null) {
411            lp = generateDefaultLayoutParams();
412        }
413
414        addViewInLayout(child, 0, lp);
415
416        child.setSelected(hasFocus());
417        if (mDisableChildrenWhenDisabled) {
418            child.setEnabled(isEnabled());
419        }
420
421        // Get measure specs
422        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
423                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
424        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
425                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
426
427        // Measure child
428        child.measure(childWidthSpec, childHeightSpec);
429
430        int childLeft;
431        int childRight;
432
433        // Position vertically based on gravity setting
434        int childTop = mSpinnerPadding.top
435                + ((getMeasuredHeight() - mSpinnerPadding.bottom -
436                        mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
437        int childBottom = childTop + child.getMeasuredHeight();
438
439        int width = child.getMeasuredWidth();
440        childLeft = 0;
441        childRight = childLeft + width;
442
443        child.layout(childLeft, childTop, childRight, childBottom);
444    }
445
446    @Override
447    public boolean performClick() {
448        boolean handled = super.performClick();
449
450        if (!handled) {
451            handled = true;
452
453            if (!mPopup.isShowing()) {
454                mPopup.show();
455            }
456        }
457
458        return handled;
459    }
460
461    public void onClick(DialogInterface dialog, int which) {
462        setSelection(which);
463        dialog.dismiss();
464    }
465
466    /**
467     * Sets the prompt to display when the dialog is shown.
468     * @param prompt the prompt to set
469     */
470    public void setPrompt(CharSequence prompt) {
471        mPopup.setPromptText(prompt);
472    }
473
474    /**
475     * Sets the prompt to display when the dialog is shown.
476     * @param promptId the resource ID of the prompt to display when the dialog is shown
477     */
478    public void setPromptId(int promptId) {
479        setPrompt(getContext().getText(promptId));
480    }
481
482    /**
483     * @return The prompt to display when the dialog is shown
484     */
485    public CharSequence getPrompt() {
486        return mPopup.getHintText();
487    }
488
489    int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
490        if (adapter == null) {
491            return 0;
492        }
493
494        int width = 0;
495        View itemView = null;
496        int itemType = 0;
497        final int widthMeasureSpec =
498            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
499        final int heightMeasureSpec =
500            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
501
502        // Make sure the number of items we'll measure is capped. If it's a huge data set
503        // with wildly varying sizes, oh well.
504        int start = Math.max(0, getSelectedItemPosition());
505        final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
506        final int count = end - start;
507        start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
508        for (int i = start; i < end; i++) {
509            final int positionType = adapter.getItemViewType(i);
510            if (positionType != itemType) {
511                itemType = positionType;
512                itemView = null;
513            }
514            itemView = adapter.getView(i, itemView, this);
515            if (itemView.getLayoutParams() == null) {
516                itemView.setLayoutParams(new ViewGroup.LayoutParams(
517                        ViewGroup.LayoutParams.WRAP_CONTENT,
518                        ViewGroup.LayoutParams.WRAP_CONTENT));
519            }
520            itemView.measure(widthMeasureSpec, heightMeasureSpec);
521            width = Math.max(width, itemView.getMeasuredWidth());
522        }
523
524        // Add background padding to measured width
525        if (background != null) {
526            background.getPadding(mTempRect);
527            width += mTempRect.left + mTempRect.right;
528        }
529
530        return width;
531    }
532
533    /**
534     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
535     * into a ListAdapter.</p>
536     */
537    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
538        private SpinnerAdapter mAdapter;
539        private ListAdapter mListAdapter;
540
541        /**
542         * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
543         *
544         * @param adapter the Adapter to transform into a ListAdapter
545         */
546        public DropDownAdapter(SpinnerAdapter adapter) {
547            this.mAdapter = adapter;
548            if (adapter instanceof ListAdapter) {
549                this.mListAdapter = (ListAdapter) adapter;
550            }
551        }
552
553        public int getCount() {
554            return mAdapter == null ? 0 : mAdapter.getCount();
555        }
556
557        public Object getItem(int position) {
558            return mAdapter == null ? null : mAdapter.getItem(position);
559        }
560
561        public long getItemId(int position) {
562            return mAdapter == null ? -1 : mAdapter.getItemId(position);
563        }
564
565        public View getView(int position, View convertView, ViewGroup parent) {
566            return getDropDownView(position, convertView, parent);
567        }
568
569        public View getDropDownView(int position, View convertView, ViewGroup parent) {
570            return mAdapter == null ? null :
571                    mAdapter.getDropDownView(position, convertView, parent);
572        }
573
574        public boolean hasStableIds() {
575            return mAdapter != null && mAdapter.hasStableIds();
576        }
577
578        public void registerDataSetObserver(DataSetObserver observer) {
579            if (mAdapter != null) {
580                mAdapter.registerDataSetObserver(observer);
581            }
582        }
583
584        public void unregisterDataSetObserver(DataSetObserver observer) {
585            if (mAdapter != null) {
586                mAdapter.unregisterDataSetObserver(observer);
587            }
588        }
589
590        /**
591         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
592         * Otherwise, return true.
593         */
594        public boolean areAllItemsEnabled() {
595            final ListAdapter adapter = mListAdapter;
596            if (adapter != null) {
597                return adapter.areAllItemsEnabled();
598            } else {
599                return true;
600            }
601        }
602
603        /**
604         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
605         * Otherwise, return true.
606         */
607        public boolean isEnabled(int position) {
608            final ListAdapter adapter = mListAdapter;
609            if (adapter != null) {
610                return adapter.isEnabled(position);
611            } else {
612                return true;
613            }
614        }
615
616        public int getItemViewType(int position) {
617            return 0;
618        }
619
620        public int getViewTypeCount() {
621            return 1;
622        }
623
624        public boolean isEmpty() {
625            return getCount() == 0;
626        }
627    }
628
629    /**
630     * Implements some sort of popup selection interface for selecting a spinner option.
631     * Allows for different spinner modes.
632     */
633    private interface SpinnerPopup {
634        public void setAdapter(ListAdapter adapter);
635
636        /**
637         * Show the popup
638         */
639        public void show();
640
641        /**
642         * Dismiss the popup
643         */
644        public void dismiss();
645
646        /**
647         * @return true if the popup is showing, false otherwise.
648         */
649        public boolean isShowing();
650
651        /**
652         * Set hint text to be displayed to the user. This should provide
653         * a description of the choice being made.
654         * @param hintText Hint text to set.
655         */
656        public void setPromptText(CharSequence hintText);
657        public CharSequence getHintText();
658    }
659
660    private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
661        private AlertDialog mPopup;
662        private ListAdapter mListAdapter;
663        private CharSequence mPrompt;
664
665        public void dismiss() {
666            mPopup.dismiss();
667            mPopup = null;
668        }
669
670        public boolean isShowing() {
671            return mPopup != null ? mPopup.isShowing() : false;
672        }
673
674        public void setAdapter(ListAdapter adapter) {
675            mListAdapter = adapter;
676        }
677
678        public void setPromptText(CharSequence hintText) {
679            mPrompt = hintText;
680        }
681
682        public CharSequence getHintText() {
683            return mPrompt;
684        }
685
686        public void show() {
687            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
688            if (mPrompt != null) {
689                builder.setTitle(mPrompt);
690            }
691            mPopup = builder.setSingleChoiceItems(mListAdapter,
692                    getSelectedItemPosition(), this).show();
693        }
694
695        public void onClick(DialogInterface dialog, int which) {
696            setSelection(which);
697            dismiss();
698        }
699    }
700
701    private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
702        private CharSequence mHintText;
703        private ListAdapter mAdapter;
704
705        public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
706            super(context, attrs, 0, defStyleRes);
707
708            setAnchorView(Spinner.this);
709            setModal(true);
710            setPromptPosition(POSITION_PROMPT_ABOVE);
711            setOnItemClickListener(new OnItemClickListener() {
712                public void onItemClick(AdapterView parent, View v, int position, long id) {
713                    Spinner.this.setSelection(position);
714                    dismiss();
715                }
716            });
717        }
718
719        @Override
720        public void setAdapter(ListAdapter adapter) {
721            super.setAdapter(adapter);
722            mAdapter = adapter;
723        }
724
725        public CharSequence getHintText() {
726            return mHintText;
727        }
728
729        public void setPromptText(CharSequence hintText) {
730            // Hint text is ignored for dropdowns, but maintain it here.
731            mHintText = hintText;
732        }
733
734        @Override
735        public void show() {
736            final Drawable background = getBackground();
737            int bgOffset = 0;
738            if (background != null) {
739                background.getPadding(mTempRect);
740                bgOffset = -mTempRect.left;
741            } else {
742                mTempRect.left = mTempRect.right = 0;
743            }
744
745            final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
746            if (mDropDownWidth == WRAP_CONTENT) {
747                final int spinnerWidth = Spinner.this.getWidth();
748                final int spinnerPaddingRight = Spinner.this.getPaddingRight();
749
750                int contentWidth =  measureContentWidth(
751                        (SpinnerAdapter) mAdapter, getBackground());
752                final int contentWidthLimit = mContext.getResources()
753                        .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
754                if (contentWidth > contentWidthLimit) {
755                    contentWidth = contentWidthLimit;
756                }
757
758                setContentWidth(Math.max(
759                       contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
760            } else if (mDropDownWidth == MATCH_PARENT) {
761                final int spinnerWidth = Spinner.this.getWidth();
762                final int spinnerPaddingRight = Spinner.this.getPaddingRight();
763                setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
764            } else {
765                setContentWidth(mDropDownWidth);
766            }
767            setHorizontalOffset(bgOffset + spinnerPaddingLeft);
768            setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
769            super.show();
770            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
771            setSelection(Spinner.this.getSelectedItemPosition());
772        }
773    }
774}
775