blob: a6e0e6d5a361b242f4b1b6288d83aa312b459e62 [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
21import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.annotation.Widget;
23import android.app.AlertDialog;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnClickListener;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080027import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.res.TypedArray;
29import android.database.DataSetObserver;
Adam Powell5f83a602011-01-19 17:58:04 -080030import android.graphics.Rect;
Adam Powell8db7cb12011-02-08 14:18:38 -080031import android.graphics.drawable.Drawable;
Alan Viveretted5269772014-06-17 16:43:45 -070032import android.os.Build;
Adam Powell235ae5f2012-12-10 13:38:03 -080033import android.os.Parcel;
34import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.util.AttributeSet;
Adam Powelld9c7be62012-03-08 19:43:43 -080036import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080037import android.view.ContextThemeWrapper;
Adam Powella39b9872011-01-05 16:07:54 -080038import android.view.Gravity;
Alan Viveretteca6a36112013-08-16 14:41:06 -070039import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.View;
41import android.view.ViewGroup;
Adam Powellf16daf62012-10-03 11:51:34 -070042import android.view.ViewTreeObserver;
43import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080044import android.view.accessibility.AccessibilityEvent;
45import 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
49
50/**
51 * A view that displays one child at a time and lets the user pick among them.
52 * The items in the Spinner come from the {@link Adapter} associated with
53 * this view.
Scott Main41ec6532010-08-19 16:57:07 -070054 *
Scott Main4c359b72012-07-24 15:51:27 -070055 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
SeongJae Park95148492012-02-29 01:56:43 +090056 *
Scott Main4c359b72012-07-24 15:51:27 -070057 * @attr ref android.R.styleable#Spinner_dropDownSelector
Scott Main4c359b72012-07-24 15:51:27 -070058 * @attr ref android.R.styleable#Spinner_dropDownWidth
59 * @attr ref android.R.styleable#Spinner_gravity
60 * @attr ref android.R.styleable#Spinner_popupBackground
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 * @attr ref android.R.styleable#Spinner_prompt
Scott Main4c359b72012-07-24 15:51:27 -070062 * @attr ref android.R.styleable#Spinner_spinnerMode
Alan Viverette40ce0702014-08-28 15:27:04 -070063 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
64 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 */
66@Widget
67public class Spinner extends AbsSpinner implements OnClickListener {
Adam Powellc3fa6302010-05-18 11:36:27 -070068 private static final String TAG = "Spinner";
SeongJae Park95148492012-02-29 01:56:43 +090069
Adam Powell50f784c2010-12-19 16:12:19 -080070 // Only measure this many items to get a decent max width.
71 private static final int MAX_ITEMS_MEASURED = 15;
72
Adam Powellc3fa6302010-05-18 11:36:27 -070073 /**
74 * Use a dialog window for selecting spinner options.
75 */
76 public static final int MODE_DIALOG = 0;
SeongJae Park95148492012-02-29 01:56:43 +090077
Adam Powellc3fa6302010-05-18 11:36:27 -070078 /**
79 * Use a dropdown anchored to the Spinner for selecting spinner options.
80 */
81 public static final int MODE_DROPDOWN = 1;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080082
Adam Powellfef364f2010-09-02 15:11:46 -070083 /**
84 * Use the theme-supplied value to select the dropdown mode.
85 */
86 private static final int MODE_THEME = -1;
Alan Viveretteca6a36112013-08-16 14:41:06 -070087
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080088 /** Context used to inflate the popup window or dialog. */
89 private Context mPopupContext;
90
Alan Viveretteca6a36112013-08-16 14:41:06 -070091 /** Forwarding listener used to implement drag-to-open. */
92 private ForwardingListener mForwardingListener;
93
Alan Viverette3f221cf2015-01-16 14:40:04 -080094 /** Temporary holder for setAdapter() calls from the super constructor. */
95 private SpinnerAdapter mTempAdapter;
96
Adam Powellc3fa6302010-05-18 11:36:27 -070097 private SpinnerPopup mPopup;
Adam Powell8db7cb12011-02-08 14:18:38 -080098 int mDropDownWidth;
Adam Powellfef364f2010-09-02 15:11:46 -070099
Adam Powella39b9872011-01-05 16:07:54 -0800100 private int mGravity;
Adam Powell42b7e992011-11-08 09:47:32 -0800101 private boolean mDisableChildrenWhenDisabled;
Adam Powella39b9872011-01-05 16:07:54 -0800102
Adam Powell19fd1642011-02-07 19:00:11 -0800103 private Rect mTempRect = new Rect();
104
Adam Powellfef364f2010-09-02 15:11:46 -0700105 /**
106 * Construct a new spinner with the given context's theme.
107 *
108 * @param context The Context the view is running in, through which it can
109 * access the current theme, resources, etc.
110 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 public Spinner(Context context) {
112 this(context, null);
113 }
114
Adam Powellfef364f2010-09-02 15:11:46 -0700115 /**
116 * Construct a new spinner with the given context's theme and the supplied
117 * mode of displaying choices. <code>mode</code> may be one of
118 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
119 *
120 * @param context The Context the view is running in, through which it can
121 * access the current theme, resources, etc.
122 * @param mode Constant describing how the user will select choices from the spinner.
123 *
124 * @see #MODE_DIALOG
125 * @see #MODE_DROPDOWN
126 */
127 public Spinner(Context context, int mode) {
128 this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
129 }
130
131 /**
132 * Construct a new spinner with the given context's theme and the supplied attribute set.
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 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 public Spinner(Context context, AttributeSet attrs) {
139 this(context, attrs, com.android.internal.R.attr.spinnerStyle);
140 }
141
Adam Powellfef364f2010-09-02 15:11:46 -0700142 /**
143 * Construct a new spinner with the given context's theme, the supplied attribute set,
Alan Viverette617feb92013-09-09 18:09:13 -0700144 * and default style attribute.
Adam Powellfef364f2010-09-02 15:11:46 -0700145 *
146 * @param context The Context the view is running in, through which it can
147 * access the current theme, resources, etc.
148 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700149 * @param defStyleAttr An attribute in the current theme that contains a
150 * reference to a style resource that supplies default values for
151 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700152 */
Alan Viverette617feb92013-09-09 18:09:13 -0700153 public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
154 this(context, attrs, defStyleAttr, 0, MODE_THEME);
Adam Powellfef364f2010-09-02 15:11:46 -0700155 }
156
157 /**
158 * Construct a new spinner with the given context's theme, the supplied attribute set,
159 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
160 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
161 *
162 * @param context The Context the view is running in, through which it can
163 * access the current theme, resources, etc.
164 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700165 * @param defStyleAttr An attribute in the current theme that contains a
166 * reference to a style resource that supplies default values for
167 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700168 * @param mode Constant describing how the user will select choices from the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700169 *
Adam Powellfef364f2010-09-02 15:11:46 -0700170 * @see #MODE_DIALOG
171 * @see #MODE_DROPDOWN
172 */
Alan Viverette617feb92013-09-09 18:09:13 -0700173 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
174 this(context, attrs, defStyleAttr, 0, mode);
175 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
Alan Viverette617feb92013-09-09 18:09:13 -0700177 /**
178 * Construct a new spinner with the given context's theme, the supplied attribute set,
179 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
180 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
181 *
182 * @param context The Context the view is running in, through which it can
183 * access the current theme, resources, etc.
184 * @param attrs The attributes of the XML tag that is inflating the view.
185 * @param defStyleAttr An attribute in the current theme that contains a
186 * reference to a style resource that supplies default values for
187 * the view. Can be 0 to not look for defaults.
188 * @param defStyleRes A resource identifier of a style resource that
189 * supplies default values for the view, used only if
190 * defStyleAttr is 0 or can not be found in the theme. Can be 0
191 * to not look for defaults.
192 * @param mode Constant describing how the user will select choices from the spinner.
193 *
194 * @see #MODE_DIALOG
195 * @see #MODE_DROPDOWN
196 */
Alan Viverette3f221cf2015-01-16 14:40:04 -0800197 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
198 int mode) {
199 this(context, attrs, defStyleAttr, defStyleRes, mode, null);
200 }
201
202 /**
203 * Constructs a new spinner with the given context's theme, the supplied
204 * attribute set, default styles, popup mode (one of {@link #MODE_DIALOG}
205 * or {@link #MODE_DROPDOWN}), and the context against which the popup
206 * should be inflated.
207 *
208 * @param context The context against which the view is inflated, which
209 * provides access to the current theme, resources, etc.
210 * @param attrs The attributes of the XML tag that is inflating the view.
211 * @param defStyleAttr An attribute in the current theme that contains a
212 * reference to a style resource that supplies default
213 * values for the view. Can be 0 to not look for
214 * defaults.
215 * @param defStyleRes A resource identifier of a style resource that
216 * supplies default values for the view, used only if
217 * defStyleAttr is 0 or can not be found in the theme.
218 * Can be 0 to not look for defaults.
219 * @param mode Constant describing how the user will select choices from
220 * the spinner.
221 * @param popupContext The context against which the dialog or dropdown
222 * popup will be inflated. Can be null to use the view
223 * context. If set, this will override any value
224 * specified by
225 * {@link android.R.styleable#Spinner_popupTheme}.
226 *
227 * @see #MODE_DIALOG
228 * @see #MODE_DROPDOWN
229 */
230 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
231 Context popupContext) {
Alan Viverette617feb92013-09-09 18:09:13 -0700232 super(context, attrs, defStyleAttr, defStyleRes);
233
234 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800235 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
Adam Powellfef364f2010-09-02 15:11:46 -0700236
Alan Viverette3f221cf2015-01-16 14:40:04 -0800237 if (popupContext != null) {
238 mPopupContext = popupContext;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800239 } else {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800240 final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0);
241 if (popupThemeResId != 0) {
242 mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
243 } else {
244 mPopupContext = context;
245 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800246 }
247
Adam Powellfef364f2010-09-02 15:11:46 -0700248 if (mode == MODE_THEME) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800249 mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
Adam Powellfef364f2010-09-02 15:11:46 -0700250 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800251
Adam Powellc3fa6302010-05-18 11:36:27 -0700252 switch (mode) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800253 case MODE_DIALOG: {
254 mPopup = new DialogPopup();
255 mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
256 break;
257 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700258
Alan Viverette3f221cf2015-01-16 14:40:04 -0800259 case MODE_DROPDOWN: {
260 final DropdownPopup popup = new DropdownPopup(
261 mPopupContext, attrs, defStyleAttr, defStyleRes);
262 final TypedArray pa = mPopupContext.obtainStyledAttributes(
263 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
264 mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth,
265 ViewGroup.LayoutParams.WRAP_CONTENT);
266 popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
267 popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
268 pa.recycle();
Adam Powellc3fa6302010-05-18 11:36:27 -0700269
Alan Viverette3f221cf2015-01-16 14:40:04 -0800270 mPopup = popup;
271 mForwardingListener = new ForwardingListener(this) {
272 @Override
273 public ListPopupWindow getPopup() {
274 return popup;
Alan Viveretteca6a36112013-08-16 14:41:06 -0700275 }
Alan Viverette3f221cf2015-01-16 14:40:04 -0800276
277 @Override
278 public boolean onForwardingStarted() {
279 if (!mPopup.isShowing()) {
280 mPopup.show(getTextDirection(), getTextAlignment());
281 }
282 return true;
283 }
284 };
285 break;
286 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700287 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800288
Alan Viverette3f221cf2015-01-16 14:40:04 -0800289 mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
Adam Powell42b7e992011-11-08 09:47:32 -0800290 mDisableChildrenWhenDisabled = a.getBoolean(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800291 R.styleable.Spinner_disableChildrenWhenDisabled, false);
Adam Powell42b7e992011-11-08 09:47:32 -0800292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700294
295 // Base constructor can call setAdapter before we initialize mPopup.
296 // Finish setting things up if this happened.
297 if (mTempAdapter != null) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800298 setAdapter(mTempAdapter);
Adam Powell68464a92010-06-07 12:48:07 -0700299 mTempAdapter = null;
300 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
Adam Powella39b9872011-01-05 16:07:54 -0800302
Adam Powelld9c7be62012-03-08 19:43:43 -0800303 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800304 * @return the context used to inflate the Spinner's popup or dialog window
305 */
306 public Context getPopupContext() {
307 return mPopupContext;
308 }
309
310 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800311 * Set the background drawable for the spinner's popup window of choices.
312 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
313 *
314 * @param background Background drawable
315 *
316 * @attr ref android.R.styleable#Spinner_popupBackground
317 */
318 public void setPopupBackgroundDrawable(Drawable background) {
319 if (!(mPopup instanceof DropdownPopup)) {
320 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
321 return;
322 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800323 mPopup.setBackgroundDrawable(background);
Adam Powelld9c7be62012-03-08 19:43:43 -0800324 }
325
326 /**
327 * Set the background drawable for the spinner's popup window of choices.
328 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
329 *
Adam Powelldca510e2012-03-08 20:06:39 -0800330 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800331 *
332 * @attr ref android.R.styleable#Spinner_popupBackground
333 */
334 public void setPopupBackgroundResource(int resId) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800335 setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800336 }
337
338 /**
339 * Get the background drawable for the spinner's popup window of choices.
340 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
341 *
342 * @return background Background drawable
343 *
344 * @attr ref android.R.styleable#Spinner_popupBackground
345 */
346 public Drawable getPopupBackground() {
347 return mPopup.getBackground();
348 }
349
350 /**
351 * Set a vertical offset in pixels for the spinner's popup window of choices.
352 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
353 *
354 * @param pixels Vertical offset in pixels
355 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700356 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800357 */
358 public void setDropDownVerticalOffset(int pixels) {
359 mPopup.setVerticalOffset(pixels);
360 }
361
362 /**
363 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
364 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
365 *
366 * @return Vertical offset in pixels
367 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700368 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800369 */
370 public int getDropDownVerticalOffset() {
371 return mPopup.getVerticalOffset();
372 }
373
374 /**
375 * Set a horizontal offset in pixels for the spinner's popup window of choices.
376 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
377 *
378 * @param pixels Horizontal offset in pixels
379 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700380 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800381 */
382 public void setDropDownHorizontalOffset(int pixels) {
383 mPopup.setHorizontalOffset(pixels);
384 }
385
386 /**
387 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
388 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
389 *
390 * @return Horizontal offset in pixels
391 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700392 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800393 */
394 public int getDropDownHorizontalOffset() {
395 return mPopup.getHorizontalOffset();
396 }
397
398 /**
399 * Set the width of the spinner's popup window of choices in pixels. This value
400 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
401 * to match the width of the Spinner itself, or
402 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
403 * of contained dropdown list items.
404 *
405 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
406 *
407 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
408 *
409 * @attr ref android.R.styleable#Spinner_dropDownWidth
410 */
411 public void setDropDownWidth(int pixels) {
412 if (!(mPopup instanceof DropdownPopup)) {
413 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
414 return;
415 }
416 mDropDownWidth = pixels;
417 }
418
419 /**
420 * Get the configured width of the spinner's popup window of choices in pixels.
421 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
422 * meaning the popup window will match the width of the Spinner itself, or
423 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
424 * of contained dropdown list items.
425 *
426 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
427 *
428 * @attr ref android.R.styleable#Spinner_dropDownWidth
429 */
430 public int getDropDownWidth() {
431 return mDropDownWidth;
432 }
433
Adam Powell42b7e992011-11-08 09:47:32 -0800434 @Override
435 public void setEnabled(boolean enabled) {
436 super.setEnabled(enabled);
437 if (mDisableChildrenWhenDisabled) {
438 final int count = getChildCount();
439 for (int i = 0; i < count; i++) {
440 getChildAt(i).setEnabled(enabled);
441 }
442 }
443 }
444
Adam Powella39b9872011-01-05 16:07:54 -0800445 /**
446 * Describes how the selected item view is positioned. Currently only the horizontal component
447 * is used. The default is determined by the current theme.
448 *
449 * @param gravity See {@link android.view.Gravity}
450 *
451 * @attr ref android.R.styleable#Spinner_gravity
452 */
453 public void setGravity(int gravity) {
454 if (mGravity != gravity) {
455 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700456 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800457 }
458 mGravity = gravity;
459 requestLayout();
460 }
461 }
462
Adam Powelld9c7be62012-03-08 19:43:43 -0800463 /**
464 * Describes how the selected item view is positioned. The default is determined by the
465 * current theme.
466 *
467 * @return A {@link android.view.Gravity Gravity} value
468 */
469 public int getGravity() {
470 return mGravity;
471 }
472
Alan Viverettea089dde2013-07-24 16:38:37 -0700473 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800474 * Sets the {@link SpinnerAdapter} used to provide the data which backs
475 * this Spinner.
Alan Viverettea089dde2013-07-24 16:38:37 -0700476 * <p>
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800477 * If this Spinner has a popup theme set in XML via the
478 * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the
479 * adapter should inflate drop-down views using the same theme. The easiest
480 * way to achieve this is by using {@link #getPopupContext()} to obtain a
481 * layout inflater for use in
482 * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}.
483 * <p>
484 * Spinner overrides {@link Adapter#getViewTypeCount()} on the
Alan Viverettea089dde2013-07-24 16:38:37 -0700485 * Adapter associated with this view. Calling
486 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
487 * returned from {@link #getAdapter()} will always return 0. Calling
488 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700489 * 1. On API {@link Build.VERSION_CODES#LOLLIPOP} and above, attempting to set an
Alan Viveretted5269772014-06-17 16:43:45 -0700490 * adapter with more than one view type will throw an
491 * {@link IllegalArgumentException}.
492 *
493 * @param adapter the adapter to set
Alan Viverettea089dde2013-07-24 16:38:37 -0700494 *
495 * @see AbsSpinner#setAdapter(SpinnerAdapter)
Alan Viveretted5269772014-06-17 16:43:45 -0700496 * @throws IllegalArgumentException if the adapter has more than one view
497 * type
Alan Viverettea089dde2013-07-24 16:38:37 -0700498 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700499 @Override
500 public void setAdapter(SpinnerAdapter adapter) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800501 // The super constructor may call setAdapter before we're prepared.
502 // Postpone doing anything until we've finished construction.
503 if (mPopup == null) {
504 mTempAdapter = adapter;
505 return;
506 }
507
Adam Powellc3fa6302010-05-18 11:36:27 -0700508 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700509
Adam Powell6a221b32013-09-10 11:38:52 -0700510 mRecycler.clear();
511
Alan Viveretted5269772014-06-17 16:43:45 -0700512 final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700513 if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
Alan Viveretted5269772014-06-17 16:43:45 -0700514 && adapter != null && adapter.getViewTypeCount() != 1) {
515 throw new IllegalArgumentException("Spinner adapter view type count must be 1");
516 }
517
Alan Viverette3f221cf2015-01-16 14:40:04 -0800518 final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
519 mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
Adam Powellc3fa6302010-05-18 11:36:27 -0700520 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521
522 @Override
523 public int getBaseline() {
524 View child = null;
525
526 if (getChildCount() > 0) {
527 child = getChildAt(0);
528 } else if (mAdapter != null && mAdapter.getCount() > 0) {
Adam Powell6a221b32013-09-10 11:38:52 -0700529 child = makeView(0, false);
Adam Powell22e92e52010-12-10 13:20:28 -0800530 mRecycler.put(0, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 }
532
533 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700534 final int childBaseline = child.getBaseline();
535 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 } else {
537 return -1;
538 }
539 }
540
Romain Guy5275d692009-07-15 17:00:23 -0700541 @Override
542 protected void onDetachedFromWindow() {
543 super.onDetachedFromWindow();
544
545 if (mPopup != null && mPopup.isShowing()) {
546 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700547 }
548 }
549
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 /**
551 * <p>A spinner does not support item click events. Calling this method
552 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700553 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 *
555 * @param l this listener will be ignored
556 */
557 @Override
558 public void setOnItemClickListener(OnItemClickListener l) {
559 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
560 }
561
Adam Powellc4e57e22012-02-24 19:24:26 -0800562 /**
563 * @hide internal use only
564 */
565 public void setOnItemClickListenerInt(OnItemClickListener l) {
566 super.setOnItemClickListener(l);
567 }
568
Adam Powella39b9872011-01-05 16:07:54 -0800569 @Override
Alan Viveretteca6a36112013-08-16 14:41:06 -0700570 public boolean onTouchEvent(MotionEvent event) {
571 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
572 return true;
573 }
574
575 return super.onTouchEvent(event);
576 }
577
578 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800579 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
580 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
581 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
582 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800583 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800584 measureContentWidth(getAdapter(), getBackground())),
585 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800586 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800587 }
588 }
589
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 /**
591 * @see android.view.View#onLayout(boolean,int,int,int,int)
592 *
593 * Creates and positions all views
594 *
595 */
596 @Override
597 protected void onLayout(boolean changed, int l, int t, int r, int b) {
598 super.onLayout(changed, l, t, r, b);
599 mInLayout = true;
600 layout(0, false);
601 mInLayout = false;
602 }
603
604 /**
605 * Creates and positions all views for this Spinner.
606 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700607 * @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 -0800608 * so views are scrolling to the left. -1 means selection is moving to the left.
609 */
610 @Override
611 void layout(int delta, boolean animate) {
612 int childrenLeft = mSpinnerPadding.left;
613 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
614
615 if (mDataChanged) {
616 handleDataChanged();
617 }
618
619 // Handle the empty set by removing all views
620 if (mItemCount == 0) {
621 resetList();
622 return;
623 }
624
625 if (mNextSelectedPosition >= 0) {
626 setSelectedPositionInt(mNextSelectedPosition);
627 }
628
629 recycleAllViews();
630
631 // Clear out old views
632 removeAllViewsInLayout();
633
Adam Powella39b9872011-01-05 16:07:54 -0800634 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700636
637 if (mAdapter != null) {
Adam Powell6a221b32013-09-10 11:38:52 -0700638 View sel = makeView(mSelectedPosition, true);
Adam Powell72d574c2013-04-10 11:27:08 -0700639 int width = sel.getMeasuredWidth();
640 int selectedOffset = childrenLeft;
641 final int layoutDirection = getLayoutDirection();
642 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
643 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
644 case Gravity.CENTER_HORIZONTAL:
645 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
646 break;
647 case Gravity.RIGHT:
648 selectedOffset = childrenLeft + childrenWidth - width;
649 break;
650 }
651 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800652 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800653
654 // Flush any cached views that did not get reused above
655 mRecycler.clear();
656
657 invalidate();
658
659 checkSelectionChanged();
660
661 mDataChanged = false;
662 mNeedSync = false;
663 setNextSelectedPositionInt(mSelectedPosition);
664 }
665
666 /**
667 * Obtain a view, either by pulling an existing view from the recycler or
668 * by getting a new one from the adapter. If we are animating, make sure
669 * there is enough information in the view's layout parameters to animate
670 * from the old to new positions.
671 *
672 * @param position Position in the spinner for the view to obtain
Adam Powell6a221b32013-09-10 11:38:52 -0700673 * @param addChild true to add the child to the spinner, false to obtain and configure only.
674 * @return A view for the given position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 */
Adam Powell6a221b32013-09-10 11:38:52 -0700676 private View makeView(int position, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800677 View child;
678
679 if (!mDataChanged) {
680 child = mRecycler.get(position);
681 if (child != null) {
682 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700683 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684
685 return child;
686 }
687 }
688
689 // Nothing found in the recycler -- ask the adapter for a view
690 child = mAdapter.getView(position, null, this);
691
692 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700693 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694
695 return child;
696 }
697
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 /**
699 * Helper for makeAndAddView to set the position of a view
700 * and fill out its layout paramters.
701 *
702 * @param child The view to position
Adam Powell6a221b32013-09-10 11:38:52 -0700703 * @param addChild true if the child should be added to the Spinner during setup
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 */
Adam Powell6a221b32013-09-10 11:38:52 -0700705 private void setUpChild(View child, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706
707 // Respect layout params that are already in the view. Otherwise
708 // make some up...
709 ViewGroup.LayoutParams lp = child.getLayoutParams();
710 if (lp == null) {
711 lp = generateDefaultLayoutParams();
712 }
713
Adam Powell6a221b32013-09-10 11:38:52 -0700714 if (addChild) {
715 addViewInLayout(child, 0, lp);
716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717
718 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800719 if (mDisableChildrenWhenDisabled) {
720 child.setEnabled(isEnabled());
721 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722
723 // Get measure specs
724 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
725 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
726 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
727 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
728
729 // Measure child
730 child.measure(childWidthSpec, childHeightSpec);
731
732 int childLeft;
733 int childRight;
734
735 // Position vertically based on gravity setting
736 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800737 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
739 int childBottom = childTop + child.getMeasuredHeight();
740
741 int width = child.getMeasuredWidth();
742 childLeft = 0;
743 childRight = childLeft + width;
744
745 child.layout(childLeft, childTop, childRight, childBottom);
746 }
747
748 @Override
749 public boolean performClick() {
750 boolean handled = super.performClick();
751
752 if (!handled) {
753 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754
Adam Powellc3fa6302010-05-18 11:36:27 -0700755 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800756 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 }
759
760 return handled;
761 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800762
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763 public void onClick(DialogInterface dialog, int which) {
764 setSelection(which);
765 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 }
767
Alan Viverettea54956a2015-01-07 16:05:02 -0800768 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800769 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800770 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
771 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800772 event.setClassName(Spinner.class.getName());
773 }
774
Alan Viverettea54956a2015-01-07 16:05:02 -0800775 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800776 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800777 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
778 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800779 info.setClassName(Spinner.class.getName());
Alan Viverette058ac7c2013-08-19 16:44:30 -0700780
781 if (mAdapter != null) {
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700782 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700783 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800784 }
785
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 /**
787 * Sets the prompt to display when the dialog is shown.
788 * @param prompt the prompt to set
789 */
790 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700791 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800792 }
793
794 /**
795 * Sets the prompt to display when the dialog is shown.
796 * @param promptId the resource ID of the prompt to display when the dialog is shown
797 */
798 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700799 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
801
802 /**
803 * @return The prompt to display when the dialog is shown
804 */
805 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700806 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800807 }
Adam Powell19fd1642011-02-07 19:00:11 -0800808
Adam Powellb70c7272011-02-10 12:04:59 -0800809 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800810 if (adapter == null) {
811 return 0;
812 }
813
814 int width = 0;
815 View itemView = null;
816 int itemType = 0;
817 final int widthMeasureSpec =
818 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
819 final int heightMeasureSpec =
820 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
821
822 // Make sure the number of items we'll measure is capped. If it's a huge data set
823 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800824 int start = Math.max(0, getSelectedItemPosition());
825 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
826 final int count = end - start;
827 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
828 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800829 final int positionType = adapter.getItemViewType(i);
830 if (positionType != itemType) {
831 itemType = positionType;
832 itemView = null;
833 }
834 itemView = adapter.getView(i, itemView, this);
835 if (itemView.getLayoutParams() == null) {
836 itemView.setLayoutParams(new ViewGroup.LayoutParams(
837 ViewGroup.LayoutParams.WRAP_CONTENT,
838 ViewGroup.LayoutParams.WRAP_CONTENT));
839 }
840 itemView.measure(widthMeasureSpec, heightMeasureSpec);
841 width = Math.max(width, itemView.getMeasuredWidth());
842 }
843
844 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800845 if (background != null) {
846 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800847 width += mTempRect.left + mTempRect.right;
848 }
849
850 return width;
851 }
852
Adam Powell235ae5f2012-12-10 13:38:03 -0800853 @Override
854 public Parcelable onSaveInstanceState() {
855 final SavedState ss = new SavedState(super.onSaveInstanceState());
856 ss.showDropdown = mPopup != null && mPopup.isShowing();
857 return ss;
858 }
859
860 @Override
861 public void onRestoreInstanceState(Parcelable state) {
862 SavedState ss = (SavedState) state;
863
864 super.onRestoreInstanceState(ss.getSuperState());
865
866 if (ss.showDropdown) {
867 ViewTreeObserver vto = getViewTreeObserver();
868 if (vto != null) {
869 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
870 @Override
871 public void onGlobalLayout() {
872 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800873 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800874 }
875 final ViewTreeObserver vto = getViewTreeObserver();
876 if (vto != null) {
877 vto.removeOnGlobalLayoutListener(this);
878 }
879 }
880 };
881 vto.addOnGlobalLayoutListener(listener);
882 }
883 }
884 }
885
886 static class SavedState extends AbsSpinner.SavedState {
887 boolean showDropdown;
888
889 SavedState(Parcelable superState) {
890 super(superState);
891 }
892
893 private SavedState(Parcel in) {
894 super(in);
895 showDropdown = in.readByte() != 0;
896 }
897
898 @Override
899 public void writeToParcel(Parcel out, int flags) {
900 super.writeToParcel(out, flags);
901 out.writeByte((byte) (showDropdown ? 1 : 0));
902 }
903
904 public static final Parcelable.Creator<SavedState> CREATOR =
905 new Parcelable.Creator<SavedState>() {
906 public SavedState createFromParcel(Parcel in) {
907 return new SavedState(in);
908 }
909
910 public SavedState[] newArray(int size) {
911 return new SavedState[size];
912 }
913 };
914 }
915
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 /**
917 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
918 * into a ListAdapter.</p>
919 */
920 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
921 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800922 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923
924 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800925 * Creates a new ListAdapter wrapper for the specified adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800926 *
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800927 * @param adapter the SpinnerAdapter to transform into a ListAdapter
928 * @param dropDownTheme the theme against which to inflate drop-down
929 * views, may be {@null} to use default theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 */
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800931 public DropDownAdapter(@Nullable SpinnerAdapter adapter,
932 @Nullable Resources.Theme dropDownTheme) {
933 mAdapter = adapter;
934
Adam Powell1f09c832010-02-18 18:13:22 -0800935 if (adapter instanceof ListAdapter) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800936 mListAdapter = (ListAdapter) adapter;
937 }
938
939 if (dropDownTheme != null && adapter instanceof Spinner.ThemedSpinnerAdapter) {
940 final Spinner.ThemedSpinnerAdapter themedAdapter =
941 (Spinner.ThemedSpinnerAdapter) adapter;
942 if (themedAdapter.getDropDownViewTheme() == null) {
943 themedAdapter.setDropDownViewTheme(dropDownTheme);
944 }
Adam Powell1f09c832010-02-18 18:13:22 -0800945 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 }
947
948 public int getCount() {
949 return mAdapter == null ? 0 : mAdapter.getCount();
950 }
951
952 public Object getItem(int position) {
953 return mAdapter == null ? null : mAdapter.getItem(position);
954 }
955
956 public long getItemId(int position) {
957 return mAdapter == null ? -1 : mAdapter.getItemId(position);
958 }
959
960 public View getView(int position, View convertView, ViewGroup parent) {
961 return getDropDownView(position, convertView, parent);
962 }
963
964 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800965 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 }
967
968 public boolean hasStableIds() {
969 return mAdapter != null && mAdapter.hasStableIds();
970 }
971
972 public void registerDataSetObserver(DataSetObserver observer) {
973 if (mAdapter != null) {
974 mAdapter.registerDataSetObserver(observer);
975 }
976 }
977
978 public void unregisterDataSetObserver(DataSetObserver observer) {
979 if (mAdapter != null) {
980 mAdapter.unregisterDataSetObserver(observer);
981 }
982 }
983
984 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800985 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
986 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 */
988 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -0800989 final ListAdapter adapter = mListAdapter;
990 if (adapter != null) {
991 return adapter.areAllItemsEnabled();
992 } else {
993 return true;
994 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800995 }
996
997 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800998 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
999 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001000 */
1001 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -08001002 final ListAdapter adapter = mListAdapter;
1003 if (adapter != null) {
1004 return adapter.isEnabled(position);
1005 } else {
1006 return true;
1007 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 }
1009
1010 public int getItemViewType(int position) {
1011 return 0;
1012 }
1013
1014 public int getViewTypeCount() {
1015 return 1;
1016 }
1017
1018 public boolean isEmpty() {
1019 return getCount() == 0;
1020 }
1021 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001022
1023 /**
1024 * Implements some sort of popup selection interface for selecting a spinner option.
1025 * Allows for different spinner modes.
1026 */
1027 private interface SpinnerPopup {
1028 public void setAdapter(ListAdapter adapter);
1029
1030 /**
1031 * Show the popup
1032 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001033 public void show(int textDirection, int textAlignment);
1034
Adam Powellc3fa6302010-05-18 11:36:27 -07001035 /**
1036 * Dismiss the popup
1037 */
1038 public void dismiss();
1039
1040 /**
1041 * @return true if the popup is showing, false otherwise.
1042 */
1043 public boolean isShowing();
1044
1045 /**
1046 * Set hint text to be displayed to the user. This should provide
1047 * a description of the choice being made.
1048 * @param hintText Hint text to set.
1049 */
1050 public void setPromptText(CharSequence hintText);
1051 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -08001052
1053 public void setBackgroundDrawable(Drawable bg);
1054 public void setVerticalOffset(int px);
1055 public void setHorizontalOffset(int px);
1056 public Drawable getBackground();
1057 public int getVerticalOffset();
1058 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -07001059 }
Alan Viverette3f221cf2015-01-16 14:40:04 -08001060
Adam Powellc3fa6302010-05-18 11:36:27 -07001061 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
1062 private AlertDialog mPopup;
1063 private ListAdapter mListAdapter;
1064 private CharSequence mPrompt;
1065
1066 public void dismiss() {
Daniel 2 Olofsson2f77f9c2013-06-10 14:49:14 +02001067 if (mPopup != null) {
1068 mPopup.dismiss();
1069 mPopup = null;
1070 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001071 }
1072
1073 public boolean isShowing() {
1074 return mPopup != null ? mPopup.isShowing() : false;
1075 }
1076
1077 public void setAdapter(ListAdapter adapter) {
1078 mListAdapter = adapter;
1079 }
1080
1081 public void setPromptText(CharSequence hintText) {
1082 mPrompt = hintText;
1083 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001084
Adam Powellc3fa6302010-05-18 11:36:27 -07001085 public CharSequence getHintText() {
1086 return mPrompt;
1087 }
1088
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001089 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -07001090 if (mListAdapter == null) {
1091 return;
1092 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001093 AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
Adam Powellc3fa6302010-05-18 11:36:27 -07001094 if (mPrompt != null) {
1095 builder.setTitle(mPrompt);
1096 }
1097 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001098 getSelectedItemPosition(), this).create();
1099 final ListView listView = mPopup.getListView();
1100 listView.setTextDirection(textDirection);
1101 listView.setTextAlignment(textAlignment);
1102 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -07001103 }
1104
1105 public void onClick(DialogInterface dialog, int which) {
1106 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -08001107 if (mOnItemClickListener != null) {
1108 performItemClick(null, which, mListAdapter.getItemId(which));
1109 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001110 dismiss();
1111 }
Adam Powelld9c7be62012-03-08 19:43:43 -08001112
1113 @Override
1114 public void setBackgroundDrawable(Drawable bg) {
1115 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1116 }
1117
1118 @Override
1119 public void setVerticalOffset(int px) {
1120 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1121 }
1122
1123 @Override
1124 public void setHorizontalOffset(int px) {
1125 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1126 }
1127
1128 @Override
1129 public Drawable getBackground() {
1130 return null;
1131 }
1132
1133 @Override
1134 public int getVerticalOffset() {
1135 return 0;
1136 }
1137
1138 @Override
1139 public int getHorizontalOffset() {
1140 return 0;
1141 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001142 }
1143
1144 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1145 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001146 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001147
Alan Viverette617feb92013-09-09 18:09:13 -07001148 public DropdownPopup(
1149 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1150 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001151
Adam Powellc3fa6302010-05-18 11:36:27 -07001152 setAnchorView(Spinner.this);
1153 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001154 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001155 setOnItemClickListener(new OnItemClickListener() {
1156 public void onItemClick(AdapterView parent, View v, int position, long id) {
1157 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001158 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001159 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001160 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001161 dismiss();
1162 }
1163 });
1164 }
1165
Adam Powell19fd1642011-02-07 19:00:11 -08001166 @Override
1167 public void setAdapter(ListAdapter adapter) {
1168 super.setAdapter(adapter);
1169 mAdapter = adapter;
1170 }
1171
Adam Powellc3fa6302010-05-18 11:36:27 -07001172 public CharSequence getHintText() {
1173 return mHintText;
1174 }
1175
1176 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001177 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001178 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001179 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001180
Adam Powell235ae5f2012-12-10 13:38:03 -08001181 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001182 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001183 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001184 if (background != null) {
1185 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001186 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001187 } else {
1188 mTempRect.left = mTempRect.right = 0;
1189 }
1190
Adam Powell62e2bde2011-08-15 15:50:05 -07001191 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001192 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1193 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001194
Adam Powell8db7cb12011-02-08 14:18:38 -08001195 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001196 int contentWidth = measureContentWidth(
1197 (SpinnerAdapter) mAdapter, getBackground());
1198 final int contentWidthLimit = mContext.getResources()
1199 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1200 if (contentWidth > contentWidthLimit) {
1201 contentWidth = contentWidthLimit;
1202 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001203 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001204 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001205 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001206 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001207 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001208 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001209 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001210
1211 if (isLayoutRtl()) {
1212 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1213 } else {
1214 hOffset += spinnerPaddingLeft;
1215 }
1216 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001217 }
1218
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001219 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001220 final boolean wasShowing = isShowing();
1221
1222 computeContentWidth();
1223
Adam Powell6f5e9342011-01-27 13:30:55 -08001224 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001225 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001226 final ListView listView = getListView();
1227 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1228 listView.setTextDirection(textDirection);
1229 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001230 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001231
Adam Powell235ae5f2012-12-10 13:38:03 -08001232 if (wasShowing) {
1233 // Skip setting up the layout/dismiss listener below. If we were previously
1234 // showing it will still stick around.
1235 return;
1236 }
1237
Adam Powellf16daf62012-10-03 11:51:34 -07001238 // Make sure we hide if our anchor goes away.
1239 // TODO: This might be appropriate to push all the way down to PopupWindow,
1240 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1241 final ViewTreeObserver vto = getViewTreeObserver();
1242 if (vto != null) {
1243 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1244 @Override
1245 public void onGlobalLayout() {
1246 if (!Spinner.this.isVisibleToUser()) {
1247 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001248 } else {
1249 computeContentWidth();
1250
1251 // Use super.show here to update; we don't want to move the selected
1252 // position or adjust other things that would be reset otherwise.
1253 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001254 }
1255 }
1256 };
1257 vto.addOnGlobalLayoutListener(layoutListener);
1258 setOnDismissListener(new OnDismissListener() {
1259 @Override public void onDismiss() {
1260 final ViewTreeObserver vto = getViewTreeObserver();
1261 if (vto != null) {
1262 vto.removeOnGlobalLayoutListener(layoutListener);
1263 }
1264 }
1265 });
1266 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001267 }
1268 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001269
1270 public interface ThemedSpinnerAdapter {
1271 /**
1272 * Sets the {@link Resources.Theme} against which drop-down views are
1273 * inflated.
1274 *
1275 * @param theme the context against which to inflate drop-down views
1276 * @see SpinnerAdapter#getDropDownView(int, View, ViewGroup)
1277 */
1278 public void setDropDownViewTheme(Resources.Theme theme);
1279
1280 /**
1281 * @return The {@link Resources.Theme} against which drop-down views are
1282 * inflated.
1283 */
1284 public Resources.Theme getDropDownViewTheme();
1285 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286}