blob: 3746ec6108bcf5a01f35e029a0e4bbd71e6a384f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080019import com.android.internal.R;
20
Tor Norbye7b9c9122013-05-30 16:48:33 -070021import android.annotation.DrawableRes;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080022import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.annotation.Widget;
24import android.app.AlertDialog;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.DialogInterface.OnClickListener;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080028import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.content.res.TypedArray;
30import android.database.DataSetObserver;
Adam Powell5f83a602011-01-19 17:58:04 -080031import android.graphics.Rect;
Adam Powell8db7cb12011-02-08 14:18:38 -080032import android.graphics.drawable.Drawable;
Alan Viveretted5269772014-06-17 16:43:45 -070033import android.os.Build;
Adam Powell235ae5f2012-12-10 13:38:03 -080034import android.os.Parcel;
35import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.util.AttributeSet;
Adam Powelld9c7be62012-03-08 19:43:43 -080037import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080038import android.view.ContextThemeWrapper;
Adam Powella39b9872011-01-05 16:07:54 -080039import android.view.Gravity;
Alan Viveretteca6a36112013-08-16 14:41:06 -070040import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.view.View;
42import android.view.ViewGroup;
Adam Powellf16daf62012-10-03 11:51:34 -070043import android.view.ViewTreeObserver;
44import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080045import android.view.accessibility.AccessibilityNodeInfo;
Alan Viveretteca6a36112013-08-16 14:41:06 -070046import android.widget.ListPopupWindow.ForwardingListener;
Adam Powellf16daf62012-10-03 11:51:34 -070047import android.widget.PopupWindow.OnDismissListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049/**
50 * A view that displays one child at a time and lets the user pick among them.
51 * The items in the Spinner come from the {@link Adapter} associated with
52 * this view.
Scott Main41ec6532010-08-19 16:57:07 -070053 *
Scott Main4c359b72012-07-24 15:51:27 -070054 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
SeongJae Park95148492012-02-29 01:56:43 +090055 *
Scott Main4c359b72012-07-24 15:51:27 -070056 * @attr ref android.R.styleable#Spinner_dropDownSelector
Scott Main4c359b72012-07-24 15:51:27 -070057 * @attr ref android.R.styleable#Spinner_dropDownWidth
58 * @attr ref android.R.styleable#Spinner_gravity
59 * @attr ref android.R.styleable#Spinner_popupBackground
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 * @attr ref android.R.styleable#Spinner_prompt
Scott Main4c359b72012-07-24 15:51:27 -070061 * @attr ref android.R.styleable#Spinner_spinnerMode
Alan Viverette40ce0702014-08-28 15:27:04 -070062 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
63 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 */
65@Widget
66public class Spinner extends AbsSpinner implements OnClickListener {
Adam Powellc3fa6302010-05-18 11:36:27 -070067 private static final String TAG = "Spinner";
SeongJae Park95148492012-02-29 01:56:43 +090068
Adam Powell50f784c2010-12-19 16:12:19 -080069 // Only measure this many items to get a decent max width.
70 private static final int MAX_ITEMS_MEASURED = 15;
71
Adam Powellc3fa6302010-05-18 11:36:27 -070072 /**
73 * Use a dialog window for selecting spinner options.
74 */
75 public static final int MODE_DIALOG = 0;
SeongJae Park95148492012-02-29 01:56:43 +090076
Adam Powellc3fa6302010-05-18 11:36:27 -070077 /**
78 * Use a dropdown anchored to the Spinner for selecting spinner options.
79 */
80 public static final int MODE_DROPDOWN = 1;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080081
Adam Powellfef364f2010-09-02 15:11:46 -070082 /**
83 * Use the theme-supplied value to select the dropdown mode.
84 */
85 private static final int MODE_THEME = -1;
Alan Viveretteca6a36112013-08-16 14:41:06 -070086
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080087 /** Context used to inflate the popup window or dialog. */
88 private Context mPopupContext;
89
Alan Viveretteca6a36112013-08-16 14:41:06 -070090 /** Forwarding listener used to implement drag-to-open. */
91 private ForwardingListener mForwardingListener;
92
Alan Viverette3f221cf2015-01-16 14:40:04 -080093 /** Temporary holder for setAdapter() calls from the super constructor. */
94 private SpinnerAdapter mTempAdapter;
95
Adam Powellc3fa6302010-05-18 11:36:27 -070096 private SpinnerPopup mPopup;
Adam Powell8db7cb12011-02-08 14:18:38 -080097 int mDropDownWidth;
Adam Powellfef364f2010-09-02 15:11:46 -070098
Adam Powella39b9872011-01-05 16:07:54 -080099 private int mGravity;
Adam Powell42b7e992011-11-08 09:47:32 -0800100 private boolean mDisableChildrenWhenDisabled;
Adam Powella39b9872011-01-05 16:07:54 -0800101
Adam Powell19fd1642011-02-07 19:00:11 -0800102 private Rect mTempRect = new Rect();
103
Adam Powellfef364f2010-09-02 15:11:46 -0700104 /**
105 * Construct a new spinner with the given context's theme.
106 *
107 * @param context The Context the view is running in, through which it can
108 * access the current theme, resources, etc.
109 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 public Spinner(Context context) {
111 this(context, null);
112 }
113
Adam Powellfef364f2010-09-02 15:11:46 -0700114 /**
115 * Construct a new spinner with the given context's theme and the supplied
116 * mode of displaying choices. <code>mode</code> may be one of
117 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
118 *
119 * @param context The Context the view is running in, through which it can
120 * access the current theme, resources, etc.
121 * @param mode Constant describing how the user will select choices from the spinner.
122 *
123 * @see #MODE_DIALOG
124 * @see #MODE_DROPDOWN
125 */
126 public Spinner(Context context, int mode) {
127 this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
128 }
129
130 /**
131 * Construct a new spinner with the given context's theme and the supplied attribute set.
132 *
133 * @param context The Context the view is running in, through which it can
134 * access the current theme, resources, etc.
135 * @param attrs The attributes of the XML tag that is inflating the view.
136 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 public Spinner(Context context, AttributeSet attrs) {
138 this(context, attrs, com.android.internal.R.attr.spinnerStyle);
139 }
140
Adam Powellfef364f2010-09-02 15:11:46 -0700141 /**
142 * Construct a new spinner with the given context's theme, the supplied attribute set,
Alan Viverette617feb92013-09-09 18:09:13 -0700143 * and default style attribute.
Adam Powellfef364f2010-09-02 15:11:46 -0700144 *
145 * @param context The Context the view is running in, through which it can
146 * access the current theme, resources, etc.
147 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700148 * @param defStyleAttr An attribute in the current theme that contains a
149 * reference to a style resource that supplies default values for
150 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700151 */
Alan Viverette617feb92013-09-09 18:09:13 -0700152 public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
153 this(context, attrs, defStyleAttr, 0, MODE_THEME);
Adam Powellfef364f2010-09-02 15:11:46 -0700154 }
155
156 /**
157 * Construct a new spinner with the given context's theme, the supplied attribute set,
158 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
159 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
160 *
161 * @param context The Context the view is running in, through which it can
162 * access the current theme, resources, etc.
163 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700164 * @param defStyleAttr An attribute in the current theme that contains a
165 * reference to a style resource that supplies default values for
166 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700167 * @param mode Constant describing how the user will select choices from the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700168 *
Adam Powellfef364f2010-09-02 15:11:46 -0700169 * @see #MODE_DIALOG
170 * @see #MODE_DROPDOWN
171 */
Alan Viverette617feb92013-09-09 18:09:13 -0700172 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
173 this(context, attrs, defStyleAttr, 0, mode);
174 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175
Alan Viverette617feb92013-09-09 18:09:13 -0700176 /**
177 * Construct a new spinner with the given context's theme, the supplied attribute set,
178 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
179 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
180 *
181 * @param context The Context the view is running in, through which it can
182 * access the current theme, resources, etc.
183 * @param attrs The attributes of the XML tag that is inflating the view.
184 * @param defStyleAttr An attribute in the current theme that contains a
185 * reference to a style resource that supplies default values for
186 * the view. Can be 0 to not look for defaults.
187 * @param defStyleRes A resource identifier of a style resource that
188 * supplies default values for the view, used only if
189 * defStyleAttr is 0 or can not be found in the theme. Can be 0
190 * to not look for defaults.
191 * @param mode Constant describing how the user will select choices from the spinner.
192 *
193 * @see #MODE_DIALOG
194 * @see #MODE_DROPDOWN
195 */
Alan Viverette3f221cf2015-01-16 14:40:04 -0800196 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
197 int mode) {
198 this(context, attrs, defStyleAttr, defStyleRes, mode, null);
199 }
200
201 /**
202 * Constructs a new spinner with the given context's theme, the supplied
203 * attribute set, default styles, popup mode (one of {@link #MODE_DIALOG}
204 * or {@link #MODE_DROPDOWN}), and the context against which the popup
205 * should be inflated.
206 *
207 * @param context The context against which the view is inflated, which
208 * provides access to the current theme, resources, etc.
209 * @param attrs The attributes of the XML tag that is inflating the view.
210 * @param defStyleAttr An attribute in the current theme that contains a
211 * reference to a style resource that supplies default
212 * values for the view. Can be 0 to not look for
213 * defaults.
214 * @param defStyleRes A resource identifier of a style resource that
215 * supplies default values for the view, used only if
216 * defStyleAttr is 0 or can not be found in the theme.
217 * Can be 0 to not look for defaults.
218 * @param mode Constant describing how the user will select choices from
219 * the spinner.
220 * @param popupContext The context against which the dialog or dropdown
221 * popup will be inflated. Can be null to use the view
222 * context. If set, this will override any value
223 * specified by
224 * {@link android.R.styleable#Spinner_popupTheme}.
225 *
226 * @see #MODE_DIALOG
227 * @see #MODE_DROPDOWN
228 */
229 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
230 Context popupContext) {
Alan Viverette617feb92013-09-09 18:09:13 -0700231 super(context, attrs, defStyleAttr, defStyleRes);
232
233 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800234 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
Adam Powellfef364f2010-09-02 15:11:46 -0700235
Alan Viverette3f221cf2015-01-16 14:40:04 -0800236 if (popupContext != null) {
237 mPopupContext = popupContext;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800238 } else {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800239 final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0);
240 if (popupThemeResId != 0) {
241 mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
242 } else {
243 mPopupContext = context;
244 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800245 }
246
Adam Powellfef364f2010-09-02 15:11:46 -0700247 if (mode == MODE_THEME) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800248 mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
Adam Powellfef364f2010-09-02 15:11:46 -0700249 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800250
Adam Powellc3fa6302010-05-18 11:36:27 -0700251 switch (mode) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800252 case MODE_DIALOG: {
253 mPopup = new DialogPopup();
254 mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
255 break;
256 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700257
Alan Viverette3f221cf2015-01-16 14:40:04 -0800258 case MODE_DROPDOWN: {
259 final DropdownPopup popup = new DropdownPopup(
260 mPopupContext, attrs, defStyleAttr, defStyleRes);
261 final TypedArray pa = mPopupContext.obtainStyledAttributes(
262 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
263 mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth,
264 ViewGroup.LayoutParams.WRAP_CONTENT);
265 popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
266 popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
267 pa.recycle();
Adam Powellc3fa6302010-05-18 11:36:27 -0700268
Alan Viverette3f221cf2015-01-16 14:40:04 -0800269 mPopup = popup;
270 mForwardingListener = new ForwardingListener(this) {
271 @Override
272 public ListPopupWindow getPopup() {
273 return popup;
Alan Viveretteca6a36112013-08-16 14:41:06 -0700274 }
Alan Viverette3f221cf2015-01-16 14:40:04 -0800275
276 @Override
277 public boolean onForwardingStarted() {
278 if (!mPopup.isShowing()) {
279 mPopup.show(getTextDirection(), getTextAlignment());
280 }
281 return true;
282 }
283 };
284 break;
285 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700286 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800287
Alan Viverette3f221cf2015-01-16 14:40:04 -0800288 mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
Adam Powell42b7e992011-11-08 09:47:32 -0800289 mDisableChildrenWhenDisabled = a.getBoolean(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800290 R.styleable.Spinner_disableChildrenWhenDisabled, false);
Adam Powell42b7e992011-11-08 09:47:32 -0800291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700293
294 // Base constructor can call setAdapter before we initialize mPopup.
295 // Finish setting things up if this happened.
296 if (mTempAdapter != null) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800297 setAdapter(mTempAdapter);
Adam Powell68464a92010-06-07 12:48:07 -0700298 mTempAdapter = null;
299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 }
Adam Powella39b9872011-01-05 16:07:54 -0800301
Adam Powelld9c7be62012-03-08 19:43:43 -0800302 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800303 * @return the context used to inflate the Spinner's popup or dialog window
304 */
305 public Context getPopupContext() {
306 return mPopupContext;
307 }
308
309 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800310 * Set the background drawable for the spinner's popup window of choices.
311 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
312 *
313 * @param background Background drawable
314 *
315 * @attr ref android.R.styleable#Spinner_popupBackground
316 */
317 public void setPopupBackgroundDrawable(Drawable background) {
318 if (!(mPopup instanceof DropdownPopup)) {
319 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
320 return;
321 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800322 mPopup.setBackgroundDrawable(background);
Adam Powelld9c7be62012-03-08 19:43:43 -0800323 }
324
325 /**
326 * Set the background drawable for the spinner's popup window of choices.
327 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
328 *
Adam Powelldca510e2012-03-08 20:06:39 -0800329 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800330 *
331 * @attr ref android.R.styleable#Spinner_popupBackground
332 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700333 public void setPopupBackgroundResource(@DrawableRes int resId) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800334 setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800335 }
336
337 /**
338 * Get the background drawable for the spinner's popup window of choices.
339 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
340 *
341 * @return background Background drawable
342 *
343 * @attr ref android.R.styleable#Spinner_popupBackground
344 */
345 public Drawable getPopupBackground() {
346 return mPopup.getBackground();
347 }
348
349 /**
350 * Set a vertical offset in pixels for the spinner's popup window of choices.
351 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
352 *
353 * @param pixels Vertical offset in pixels
354 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700355 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800356 */
357 public void setDropDownVerticalOffset(int pixels) {
358 mPopup.setVerticalOffset(pixels);
359 }
360
361 /**
362 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
363 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
364 *
365 * @return Vertical offset in pixels
366 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700367 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800368 */
369 public int getDropDownVerticalOffset() {
370 return mPopup.getVerticalOffset();
371 }
372
373 /**
374 * Set a horizontal offset in pixels for the spinner's popup window of choices.
375 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
376 *
377 * @param pixels Horizontal offset in pixels
378 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700379 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800380 */
381 public void setDropDownHorizontalOffset(int pixels) {
382 mPopup.setHorizontalOffset(pixels);
383 }
384
385 /**
386 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
387 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
388 *
389 * @return Horizontal offset in pixels
390 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700391 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800392 */
393 public int getDropDownHorizontalOffset() {
394 return mPopup.getHorizontalOffset();
395 }
396
397 /**
398 * Set the width of the spinner's popup window of choices in pixels. This value
399 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
400 * to match the width of the Spinner itself, or
401 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
402 * of contained dropdown list items.
403 *
404 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
405 *
406 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
407 *
408 * @attr ref android.R.styleable#Spinner_dropDownWidth
409 */
410 public void setDropDownWidth(int pixels) {
411 if (!(mPopup instanceof DropdownPopup)) {
412 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
413 return;
414 }
415 mDropDownWidth = pixels;
416 }
417
418 /**
419 * Get the configured width of the spinner's popup window of choices in pixels.
420 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
421 * meaning the popup window will match the width of the Spinner itself, or
422 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
423 * of contained dropdown list items.
424 *
425 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
426 *
427 * @attr ref android.R.styleable#Spinner_dropDownWidth
428 */
429 public int getDropDownWidth() {
430 return mDropDownWidth;
431 }
432
Adam Powell42b7e992011-11-08 09:47:32 -0800433 @Override
434 public void setEnabled(boolean enabled) {
435 super.setEnabled(enabled);
436 if (mDisableChildrenWhenDisabled) {
437 final int count = getChildCount();
438 for (int i = 0; i < count; i++) {
439 getChildAt(i).setEnabled(enabled);
440 }
441 }
442 }
443
Adam Powella39b9872011-01-05 16:07:54 -0800444 /**
445 * Describes how the selected item view is positioned. Currently only the horizontal component
446 * is used. The default is determined by the current theme.
447 *
448 * @param gravity See {@link android.view.Gravity}
449 *
450 * @attr ref android.R.styleable#Spinner_gravity
451 */
452 public void setGravity(int gravity) {
453 if (mGravity != gravity) {
454 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700455 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800456 }
457 mGravity = gravity;
458 requestLayout();
459 }
460 }
461
Adam Powelld9c7be62012-03-08 19:43:43 -0800462 /**
463 * Describes how the selected item view is positioned. The default is determined by the
464 * current theme.
465 *
466 * @return A {@link android.view.Gravity Gravity} value
467 */
468 public int getGravity() {
469 return mGravity;
470 }
471
Alan Viverettea089dde2013-07-24 16:38:37 -0700472 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800473 * Sets the {@link SpinnerAdapter} used to provide the data which backs
474 * this Spinner.
Alan Viverettea089dde2013-07-24 16:38:37 -0700475 * <p>
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800476 * If this Spinner has a popup theme set in XML via the
477 * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the
478 * adapter should inflate drop-down views using the same theme. The easiest
479 * way to achieve this is by using {@link #getPopupContext()} to obtain a
480 * layout inflater for use in
481 * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}.
482 * <p>
483 * Spinner overrides {@link Adapter#getViewTypeCount()} on the
Alan Viverettea089dde2013-07-24 16:38:37 -0700484 * Adapter associated with this view. Calling
485 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
486 * returned from {@link #getAdapter()} will always return 0. Calling
487 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700488 * 1. On API {@link Build.VERSION_CODES#LOLLIPOP} and above, attempting to set an
Alan Viveretted5269772014-06-17 16:43:45 -0700489 * adapter with more than one view type will throw an
490 * {@link IllegalArgumentException}.
491 *
492 * @param adapter the adapter to set
Alan Viverettea089dde2013-07-24 16:38:37 -0700493 *
494 * @see AbsSpinner#setAdapter(SpinnerAdapter)
Alan Viveretted5269772014-06-17 16:43:45 -0700495 * @throws IllegalArgumentException if the adapter has more than one view
496 * type
Alan Viverettea089dde2013-07-24 16:38:37 -0700497 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700498 @Override
499 public void setAdapter(SpinnerAdapter adapter) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800500 // The super constructor may call setAdapter before we're prepared.
501 // Postpone doing anything until we've finished construction.
502 if (mPopup == null) {
503 mTempAdapter = adapter;
504 return;
505 }
506
Adam Powellc3fa6302010-05-18 11:36:27 -0700507 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700508
Adam Powell6a221b32013-09-10 11:38:52 -0700509 mRecycler.clear();
510
Alan Viveretted5269772014-06-17 16:43:45 -0700511 final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700512 if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
Alan Viveretted5269772014-06-17 16:43:45 -0700513 && adapter != null && adapter.getViewTypeCount() != 1) {
514 throw new IllegalArgumentException("Spinner adapter view type count must be 1");
515 }
516
Alan Viverette3f221cf2015-01-16 14:40:04 -0800517 final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
518 mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
Adam Powellc3fa6302010-05-18 11:36:27 -0700519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520
521 @Override
522 public int getBaseline() {
523 View child = null;
524
525 if (getChildCount() > 0) {
526 child = getChildAt(0);
527 } else if (mAdapter != null && mAdapter.getCount() > 0) {
Adam Powell6a221b32013-09-10 11:38:52 -0700528 child = makeView(0, false);
Adam Powell22e92e52010-12-10 13:20:28 -0800529 mRecycler.put(0, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 }
531
532 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700533 final int childBaseline = child.getBaseline();
534 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 } else {
536 return -1;
537 }
538 }
539
Romain Guy5275d692009-07-15 17:00:23 -0700540 @Override
541 protected void onDetachedFromWindow() {
542 super.onDetachedFromWindow();
543
544 if (mPopup != null && mPopup.isShowing()) {
545 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700546 }
547 }
548
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 /**
550 * <p>A spinner does not support item click events. Calling this method
551 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700552 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800553 *
554 * @param l this listener will be ignored
555 */
556 @Override
557 public void setOnItemClickListener(OnItemClickListener l) {
558 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
559 }
560
Adam Powellc4e57e22012-02-24 19:24:26 -0800561 /**
562 * @hide internal use only
563 */
564 public void setOnItemClickListenerInt(OnItemClickListener l) {
565 super.setOnItemClickListener(l);
566 }
567
Adam Powella39b9872011-01-05 16:07:54 -0800568 @Override
Alan Viveretteca6a36112013-08-16 14:41:06 -0700569 public boolean onTouchEvent(MotionEvent event) {
570 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
571 return true;
572 }
573
574 return super.onTouchEvent(event);
575 }
576
577 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800578 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
579 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
580 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
581 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800582 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800583 measureContentWidth(getAdapter(), getBackground())),
584 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800585 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800586 }
587 }
588
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 /**
590 * @see android.view.View#onLayout(boolean,int,int,int,int)
591 *
592 * Creates and positions all views
593 *
594 */
595 @Override
596 protected void onLayout(boolean changed, int l, int t, int r, int b) {
597 super.onLayout(changed, l, t, r, b);
598 mInLayout = true;
599 layout(0, false);
600 mInLayout = false;
601 }
602
603 /**
604 * Creates and positions all views for this Spinner.
605 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700606 * @param delta Change in the selected position. +1 means selection is moving to the right,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 * so views are scrolling to the left. -1 means selection is moving to the left.
608 */
609 @Override
610 void layout(int delta, boolean animate) {
611 int childrenLeft = mSpinnerPadding.left;
612 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
613
614 if (mDataChanged) {
615 handleDataChanged();
616 }
617
618 // Handle the empty set by removing all views
619 if (mItemCount == 0) {
620 resetList();
621 return;
622 }
623
624 if (mNextSelectedPosition >= 0) {
625 setSelectedPositionInt(mNextSelectedPosition);
626 }
627
628 recycleAllViews();
629
630 // Clear out old views
631 removeAllViewsInLayout();
632
Adam Powella39b9872011-01-05 16:07:54 -0800633 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800634 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700635
636 if (mAdapter != null) {
Adam Powell6a221b32013-09-10 11:38:52 -0700637 View sel = makeView(mSelectedPosition, true);
Adam Powell72d574c2013-04-10 11:27:08 -0700638 int width = sel.getMeasuredWidth();
639 int selectedOffset = childrenLeft;
640 final int layoutDirection = getLayoutDirection();
641 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
642 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
643 case Gravity.CENTER_HORIZONTAL:
644 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
645 break;
646 case Gravity.RIGHT:
647 selectedOffset = childrenLeft + childrenWidth - width;
648 break;
649 }
650 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800651 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
653 // Flush any cached views that did not get reused above
654 mRecycler.clear();
655
656 invalidate();
657
658 checkSelectionChanged();
659
660 mDataChanged = false;
661 mNeedSync = false;
662 setNextSelectedPositionInt(mSelectedPosition);
663 }
664
665 /**
666 * Obtain a view, either by pulling an existing view from the recycler or
667 * by getting a new one from the adapter. If we are animating, make sure
668 * there is enough information in the view's layout parameters to animate
669 * from the old to new positions.
670 *
671 * @param position Position in the spinner for the view to obtain
Adam Powell6a221b32013-09-10 11:38:52 -0700672 * @param addChild true to add the child to the spinner, false to obtain and configure only.
673 * @return A view for the given position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800674 */
Adam Powell6a221b32013-09-10 11:38:52 -0700675 private View makeView(int position, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 View child;
677
678 if (!mDataChanged) {
679 child = mRecycler.get(position);
680 if (child != null) {
681 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700682 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683
684 return child;
685 }
686 }
687
688 // Nothing found in the recycler -- ask the adapter for a view
689 child = mAdapter.getView(position, null, this);
690
691 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700692 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693
694 return child;
695 }
696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 /**
698 * Helper for makeAndAddView to set the position of a view
699 * and fill out its layout paramters.
700 *
701 * @param child The view to position
Adam Powell6a221b32013-09-10 11:38:52 -0700702 * @param addChild true if the child should be added to the Spinner during setup
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 */
Adam Powell6a221b32013-09-10 11:38:52 -0700704 private void setUpChild(View child, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705
706 // Respect layout params that are already in the view. Otherwise
707 // make some up...
708 ViewGroup.LayoutParams lp = child.getLayoutParams();
709 if (lp == null) {
710 lp = generateDefaultLayoutParams();
711 }
712
Adam Powell6a221b32013-09-10 11:38:52 -0700713 if (addChild) {
714 addViewInLayout(child, 0, lp);
715 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716
717 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800718 if (mDisableChildrenWhenDisabled) {
719 child.setEnabled(isEnabled());
720 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721
722 // Get measure specs
723 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
724 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
725 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
726 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
727
728 // Measure child
729 child.measure(childWidthSpec, childHeightSpec);
730
731 int childLeft;
732 int childRight;
733
734 // Position vertically based on gravity setting
735 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800736 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
738 int childBottom = childTop + child.getMeasuredHeight();
739
740 int width = child.getMeasuredWidth();
741 childLeft = 0;
742 childRight = childLeft + width;
743
744 child.layout(childLeft, childTop, childRight, childBottom);
745 }
746
747 @Override
748 public boolean performClick() {
749 boolean handled = super.performClick();
750
751 if (!handled) {
752 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753
Adam Powellc3fa6302010-05-18 11:36:27 -0700754 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800755 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 }
758
759 return handled;
760 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800761
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 public void onClick(DialogInterface dialog, int which) {
763 setSelection(which);
764 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 }
766
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800767 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800768 public CharSequence getAccessibilityClassName() {
769 return Spinner.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800770 }
771
Alan Viverettea54956a2015-01-07 16:05:02 -0800772 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800773 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800774 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
775 super.onInitializeAccessibilityNodeInfoInternal(info);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700776
777 if (mAdapter != null) {
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700778 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700779 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800780 }
781
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 /**
783 * Sets the prompt to display when the dialog is shown.
784 * @param prompt the prompt to set
785 */
786 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700787 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 }
789
790 /**
791 * Sets the prompt to display when the dialog is shown.
792 * @param promptId the resource ID of the prompt to display when the dialog is shown
793 */
794 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700795 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800796 }
797
798 /**
799 * @return The prompt to display when the dialog is shown
800 */
801 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700802 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 }
Adam Powell19fd1642011-02-07 19:00:11 -0800804
Adam Powellb70c7272011-02-10 12:04:59 -0800805 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800806 if (adapter == null) {
807 return 0;
808 }
809
810 int width = 0;
811 View itemView = null;
812 int itemType = 0;
813 final int widthMeasureSpec =
814 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
815 final int heightMeasureSpec =
816 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
817
818 // Make sure the number of items we'll measure is capped. If it's a huge data set
819 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800820 int start = Math.max(0, getSelectedItemPosition());
821 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
822 final int count = end - start;
823 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
824 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800825 final int positionType = adapter.getItemViewType(i);
826 if (positionType != itemType) {
827 itemType = positionType;
828 itemView = null;
829 }
830 itemView = adapter.getView(i, itemView, this);
831 if (itemView.getLayoutParams() == null) {
832 itemView.setLayoutParams(new ViewGroup.LayoutParams(
833 ViewGroup.LayoutParams.WRAP_CONTENT,
834 ViewGroup.LayoutParams.WRAP_CONTENT));
835 }
836 itemView.measure(widthMeasureSpec, heightMeasureSpec);
837 width = Math.max(width, itemView.getMeasuredWidth());
838 }
839
840 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800841 if (background != null) {
842 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800843 width += mTempRect.left + mTempRect.right;
844 }
845
846 return width;
847 }
848
Adam Powell235ae5f2012-12-10 13:38:03 -0800849 @Override
850 public Parcelable onSaveInstanceState() {
851 final SavedState ss = new SavedState(super.onSaveInstanceState());
852 ss.showDropdown = mPopup != null && mPopup.isShowing();
853 return ss;
854 }
855
856 @Override
857 public void onRestoreInstanceState(Parcelable state) {
858 SavedState ss = (SavedState) state;
859
860 super.onRestoreInstanceState(ss.getSuperState());
861
862 if (ss.showDropdown) {
863 ViewTreeObserver vto = getViewTreeObserver();
864 if (vto != null) {
865 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
866 @Override
867 public void onGlobalLayout() {
868 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800869 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800870 }
871 final ViewTreeObserver vto = getViewTreeObserver();
872 if (vto != null) {
873 vto.removeOnGlobalLayoutListener(this);
874 }
875 }
876 };
877 vto.addOnGlobalLayoutListener(listener);
878 }
879 }
880 }
881
882 static class SavedState extends AbsSpinner.SavedState {
883 boolean showDropdown;
884
885 SavedState(Parcelable superState) {
886 super(superState);
887 }
888
889 private SavedState(Parcel in) {
890 super(in);
891 showDropdown = in.readByte() != 0;
892 }
893
894 @Override
895 public void writeToParcel(Parcel out, int flags) {
896 super.writeToParcel(out, flags);
897 out.writeByte((byte) (showDropdown ? 1 : 0));
898 }
899
900 public static final Parcelable.Creator<SavedState> CREATOR =
901 new Parcelable.Creator<SavedState>() {
902 public SavedState createFromParcel(Parcel in) {
903 return new SavedState(in);
904 }
905
906 public SavedState[] newArray(int size) {
907 return new SavedState[size];
908 }
909 };
910 }
911
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 /**
913 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
914 * into a ListAdapter.</p>
915 */
916 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
917 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800918 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919
920 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800921 * Creates a new ListAdapter wrapper for the specified adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800922 *
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800923 * @param adapter the SpinnerAdapter to transform into a ListAdapter
924 * @param dropDownTheme the theme against which to inflate drop-down
925 * views, may be {@null} to use default theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 */
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800927 public DropDownAdapter(@Nullable SpinnerAdapter adapter,
928 @Nullable Resources.Theme dropDownTheme) {
929 mAdapter = adapter;
930
Adam Powell1f09c832010-02-18 18:13:22 -0800931 if (adapter instanceof ListAdapter) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800932 mListAdapter = (ListAdapter) adapter;
933 }
934
935 if (dropDownTheme != null && adapter instanceof Spinner.ThemedSpinnerAdapter) {
936 final Spinner.ThemedSpinnerAdapter themedAdapter =
937 (Spinner.ThemedSpinnerAdapter) adapter;
938 if (themedAdapter.getDropDownViewTheme() == null) {
939 themedAdapter.setDropDownViewTheme(dropDownTheme);
940 }
Adam Powell1f09c832010-02-18 18:13:22 -0800941 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800942 }
943
944 public int getCount() {
945 return mAdapter == null ? 0 : mAdapter.getCount();
946 }
947
948 public Object getItem(int position) {
949 return mAdapter == null ? null : mAdapter.getItem(position);
950 }
951
952 public long getItemId(int position) {
953 return mAdapter == null ? -1 : mAdapter.getItemId(position);
954 }
955
956 public View getView(int position, View convertView, ViewGroup parent) {
957 return getDropDownView(position, convertView, parent);
958 }
959
960 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800961 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800962 }
963
964 public boolean hasStableIds() {
965 return mAdapter != null && mAdapter.hasStableIds();
966 }
967
968 public void registerDataSetObserver(DataSetObserver observer) {
969 if (mAdapter != null) {
970 mAdapter.registerDataSetObserver(observer);
971 }
972 }
973
974 public void unregisterDataSetObserver(DataSetObserver observer) {
975 if (mAdapter != null) {
976 mAdapter.unregisterDataSetObserver(observer);
977 }
978 }
979
980 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800981 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
982 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800983 */
984 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -0800985 final ListAdapter adapter = mListAdapter;
986 if (adapter != null) {
987 return adapter.areAllItemsEnabled();
988 } else {
989 return true;
990 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800991 }
992
993 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800994 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
995 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 */
997 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -0800998 final ListAdapter adapter = mListAdapter;
999 if (adapter != null) {
1000 return adapter.isEnabled(position);
1001 } else {
1002 return true;
1003 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 }
1005
1006 public int getItemViewType(int position) {
1007 return 0;
1008 }
1009
1010 public int getViewTypeCount() {
1011 return 1;
1012 }
1013
1014 public boolean isEmpty() {
1015 return getCount() == 0;
1016 }
1017 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001018
1019 /**
1020 * Implements some sort of popup selection interface for selecting a spinner option.
1021 * Allows for different spinner modes.
1022 */
1023 private interface SpinnerPopup {
1024 public void setAdapter(ListAdapter adapter);
1025
1026 /**
1027 * Show the popup
1028 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001029 public void show(int textDirection, int textAlignment);
1030
Adam Powellc3fa6302010-05-18 11:36:27 -07001031 /**
1032 * Dismiss the popup
1033 */
1034 public void dismiss();
1035
1036 /**
1037 * @return true if the popup is showing, false otherwise.
1038 */
1039 public boolean isShowing();
1040
1041 /**
1042 * Set hint text to be displayed to the user. This should provide
1043 * a description of the choice being made.
1044 * @param hintText Hint text to set.
1045 */
1046 public void setPromptText(CharSequence hintText);
1047 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -08001048
1049 public void setBackgroundDrawable(Drawable bg);
1050 public void setVerticalOffset(int px);
1051 public void setHorizontalOffset(int px);
1052 public Drawable getBackground();
1053 public int getVerticalOffset();
1054 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -07001055 }
Alan Viverette3f221cf2015-01-16 14:40:04 -08001056
Adam Powellc3fa6302010-05-18 11:36:27 -07001057 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
1058 private AlertDialog mPopup;
1059 private ListAdapter mListAdapter;
1060 private CharSequence mPrompt;
1061
1062 public void dismiss() {
Daniel 2 Olofsson2f77f9c2013-06-10 14:49:14 +02001063 if (mPopup != null) {
1064 mPopup.dismiss();
1065 mPopup = null;
1066 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001067 }
1068
1069 public boolean isShowing() {
1070 return mPopup != null ? mPopup.isShowing() : false;
1071 }
1072
1073 public void setAdapter(ListAdapter adapter) {
1074 mListAdapter = adapter;
1075 }
1076
1077 public void setPromptText(CharSequence hintText) {
1078 mPrompt = hintText;
1079 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001080
Adam Powellc3fa6302010-05-18 11:36:27 -07001081 public CharSequence getHintText() {
1082 return mPrompt;
1083 }
1084
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001085 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -07001086 if (mListAdapter == null) {
1087 return;
1088 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001089 AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
Adam Powellc3fa6302010-05-18 11:36:27 -07001090 if (mPrompt != null) {
1091 builder.setTitle(mPrompt);
1092 }
1093 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001094 getSelectedItemPosition(), this).create();
1095 final ListView listView = mPopup.getListView();
1096 listView.setTextDirection(textDirection);
1097 listView.setTextAlignment(textAlignment);
1098 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -07001099 }
1100
1101 public void onClick(DialogInterface dialog, int which) {
1102 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -08001103 if (mOnItemClickListener != null) {
1104 performItemClick(null, which, mListAdapter.getItemId(which));
1105 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001106 dismiss();
1107 }
Adam Powelld9c7be62012-03-08 19:43:43 -08001108
1109 @Override
1110 public void setBackgroundDrawable(Drawable bg) {
1111 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1112 }
1113
1114 @Override
1115 public void setVerticalOffset(int px) {
1116 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1117 }
1118
1119 @Override
1120 public void setHorizontalOffset(int px) {
1121 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1122 }
1123
1124 @Override
1125 public Drawable getBackground() {
1126 return null;
1127 }
1128
1129 @Override
1130 public int getVerticalOffset() {
1131 return 0;
1132 }
1133
1134 @Override
1135 public int getHorizontalOffset() {
1136 return 0;
1137 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001138 }
1139
1140 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1141 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001142 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001143
Alan Viverette617feb92013-09-09 18:09:13 -07001144 public DropdownPopup(
1145 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1146 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001147
Adam Powellc3fa6302010-05-18 11:36:27 -07001148 setAnchorView(Spinner.this);
1149 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001150 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001151 setOnItemClickListener(new OnItemClickListener() {
1152 public void onItemClick(AdapterView parent, View v, int position, long id) {
1153 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001154 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001155 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001156 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001157 dismiss();
1158 }
1159 });
1160 }
1161
Adam Powell19fd1642011-02-07 19:00:11 -08001162 @Override
1163 public void setAdapter(ListAdapter adapter) {
1164 super.setAdapter(adapter);
1165 mAdapter = adapter;
1166 }
1167
Adam Powellc3fa6302010-05-18 11:36:27 -07001168 public CharSequence getHintText() {
1169 return mHintText;
1170 }
1171
1172 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001173 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001174 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001175 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001176
Adam Powell235ae5f2012-12-10 13:38:03 -08001177 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001178 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001179 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001180 if (background != null) {
1181 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001182 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001183 } else {
1184 mTempRect.left = mTempRect.right = 0;
1185 }
1186
Adam Powell62e2bde2011-08-15 15:50:05 -07001187 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001188 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1189 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001190
Adam Powell8db7cb12011-02-08 14:18:38 -08001191 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001192 int contentWidth = measureContentWidth(
1193 (SpinnerAdapter) mAdapter, getBackground());
1194 final int contentWidthLimit = mContext.getResources()
1195 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1196 if (contentWidth > contentWidthLimit) {
1197 contentWidth = contentWidthLimit;
1198 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001199 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001200 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001201 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001202 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001203 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001204 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001205 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001206
1207 if (isLayoutRtl()) {
1208 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1209 } else {
1210 hOffset += spinnerPaddingLeft;
1211 }
1212 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001213 }
1214
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001215 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001216 final boolean wasShowing = isShowing();
1217
1218 computeContentWidth();
1219
Adam Powell6f5e9342011-01-27 13:30:55 -08001220 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001221 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001222 final ListView listView = getListView();
1223 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1224 listView.setTextDirection(textDirection);
1225 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001226 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001227
Adam Powell235ae5f2012-12-10 13:38:03 -08001228 if (wasShowing) {
1229 // Skip setting up the layout/dismiss listener below. If we were previously
1230 // showing it will still stick around.
1231 return;
1232 }
1233
Adam Powellf16daf62012-10-03 11:51:34 -07001234 // Make sure we hide if our anchor goes away.
1235 // TODO: This might be appropriate to push all the way down to PopupWindow,
1236 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1237 final ViewTreeObserver vto = getViewTreeObserver();
1238 if (vto != null) {
1239 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1240 @Override
1241 public void onGlobalLayout() {
1242 if (!Spinner.this.isVisibleToUser()) {
1243 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001244 } else {
1245 computeContentWidth();
1246
1247 // Use super.show here to update; we don't want to move the selected
1248 // position or adjust other things that would be reset otherwise.
1249 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001250 }
1251 }
1252 };
1253 vto.addOnGlobalLayoutListener(layoutListener);
1254 setOnDismissListener(new OnDismissListener() {
1255 @Override public void onDismiss() {
1256 final ViewTreeObserver vto = getViewTreeObserver();
1257 if (vto != null) {
1258 vto.removeOnGlobalLayoutListener(layoutListener);
1259 }
1260 }
1261 });
1262 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001263 }
1264 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001265
1266 public interface ThemedSpinnerAdapter {
1267 /**
1268 * Sets the {@link Resources.Theme} against which drop-down views are
1269 * inflated.
1270 *
1271 * @param theme the context against which to inflate drop-down views
1272 * @see SpinnerAdapter#getDropDownView(int, View, ViewGroup)
1273 */
1274 public void setDropDownViewTheme(Resources.Theme theme);
1275
1276 /**
1277 * @return The {@link Resources.Theme} against which drop-down views are
1278 * inflated.
1279 */
1280 public Resources.Theme getDropDownViewTheme();
1281 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282}