blob: 9601d4a703dfb435f3a0ed9e581903d57d41a7ec [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
19import android.annotation.Widget;
20import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnClickListener;
24import android.content.res.TypedArray;
25import android.database.DataSetObserver;
Adam Powell5f83a602011-01-19 17:58:04 -080026import android.graphics.Rect;
Adam Powell8db7cb12011-02-08 14:18:38 -080027import android.graphics.drawable.Drawable;
Adam Powell235ae5f2012-12-10 13:38:03 -080028import android.os.Parcel;
29import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.util.AttributeSet;
Adam Powelld9c7be62012-03-08 19:43:43 -080031import android.util.Log;
Adam Powella39b9872011-01-05 16:07:54 -080032import android.view.Gravity;
Alan Viveretteca6a36112013-08-16 14:41:06 -070033import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.view.View;
35import android.view.ViewGroup;
Adam Powellf16daf62012-10-03 11:51:34 -070036import android.view.ViewTreeObserver;
37import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080038import android.view.accessibility.AccessibilityEvent;
39import android.view.accessibility.AccessibilityNodeInfo;
Alan Viveretteca6a36112013-08-16 14:41:06 -070040import android.widget.ListPopupWindow.ForwardingListener;
Adam Powellf16daf62012-10-03 11:51:34 -070041import android.widget.PopupWindow.OnDismissListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
43
44/**
45 * A view that displays one child at a time and lets the user pick among them.
46 * The items in the Spinner come from the {@link Adapter} associated with
47 * this view.
Scott Main41ec6532010-08-19 16:57:07 -070048 *
Scott Main4c359b72012-07-24 15:51:27 -070049 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
SeongJae Park95148492012-02-29 01:56:43 +090050 *
Scott Main4c359b72012-07-24 15:51:27 -070051 * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
52 * @attr ref android.R.styleable#Spinner_dropDownSelector
53 * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
54 * @attr ref android.R.styleable#Spinner_dropDownWidth
55 * @attr ref android.R.styleable#Spinner_gravity
56 * @attr ref android.R.styleable#Spinner_popupBackground
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 * @attr ref android.R.styleable#Spinner_prompt
Scott Main4c359b72012-07-24 15:51:27 -070058 * @attr ref android.R.styleable#Spinner_spinnerMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 */
60@Widget
61public class Spinner extends AbsSpinner implements OnClickListener {
Adam Powellc3fa6302010-05-18 11:36:27 -070062 private static final String TAG = "Spinner";
SeongJae Park95148492012-02-29 01:56:43 +090063
Adam Powell50f784c2010-12-19 16:12:19 -080064 // Only measure this many items to get a decent max width.
65 private static final int MAX_ITEMS_MEASURED = 15;
66
Adam Powellc3fa6302010-05-18 11:36:27 -070067 /**
68 * Use a dialog window for selecting spinner options.
69 */
70 public static final int MODE_DIALOG = 0;
SeongJae Park95148492012-02-29 01:56:43 +090071
Adam Powellc3fa6302010-05-18 11:36:27 -070072 /**
73 * Use a dropdown anchored to the Spinner for selecting spinner options.
74 */
75 public static final int MODE_DROPDOWN = 1;
76
Adam Powellfef364f2010-09-02 15:11:46 -070077 /**
78 * Use the theme-supplied value to select the dropdown mode.
79 */
80 private static final int MODE_THEME = -1;
Alan Viveretteca6a36112013-08-16 14:41:06 -070081
82 /** Forwarding listener used to implement drag-to-open. */
83 private ForwardingListener mForwardingListener;
84
Adam Powellc3fa6302010-05-18 11:36:27 -070085 private SpinnerPopup mPopup;
Adam Powell68464a92010-06-07 12:48:07 -070086 private DropDownAdapter mTempAdapter;
Adam Powell8db7cb12011-02-08 14:18:38 -080087 int mDropDownWidth;
Adam Powellfef364f2010-09-02 15:11:46 -070088
Adam Powella39b9872011-01-05 16:07:54 -080089 private int mGravity;
Adam Powell42b7e992011-11-08 09:47:32 -080090 private boolean mDisableChildrenWhenDisabled;
Adam Powella39b9872011-01-05 16:07:54 -080091
Adam Powell19fd1642011-02-07 19:00:11 -080092 private Rect mTempRect = new Rect();
93
Adam Powellfef364f2010-09-02 15:11:46 -070094 /**
95 * Construct a new spinner with the given context's theme.
96 *
97 * @param context The Context the view is running in, through which it can
98 * access the current theme, resources, etc.
99 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 public Spinner(Context context) {
101 this(context, null);
102 }
103
Adam Powellfef364f2010-09-02 15:11:46 -0700104 /**
105 * Construct a new spinner with the given context's theme and the supplied
106 * mode of displaying choices. <code>mode</code> may be one of
107 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
108 *
109 * @param context The Context the view is running in, through which it can
110 * access the current theme, resources, etc.
111 * @param mode Constant describing how the user will select choices from the spinner.
112 *
113 * @see #MODE_DIALOG
114 * @see #MODE_DROPDOWN
115 */
116 public Spinner(Context context, int mode) {
117 this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
118 }
119
120 /**
121 * Construct a new spinner with the given context's theme and the supplied attribute set.
122 *
123 * @param context The Context the view is running in, through which it can
124 * access the current theme, resources, etc.
125 * @param attrs The attributes of the XML tag that is inflating the view.
126 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 public Spinner(Context context, AttributeSet attrs) {
128 this(context, attrs, com.android.internal.R.attr.spinnerStyle);
129 }
130
Adam Powellfef364f2010-09-02 15:11:46 -0700131 /**
132 * Construct a new spinner with the given context's theme, the supplied attribute set,
Alan Viverette617feb92013-09-09 18:09:13 -0700133 * and default style attribute.
Adam Powellfef364f2010-09-02 15:11:46 -0700134 *
135 * @param context The Context the view is running in, through which it can
136 * access the current theme, resources, etc.
137 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700138 * @param defStyleAttr An attribute in the current theme that contains a
139 * reference to a style resource that supplies default values for
140 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700141 */
Alan Viverette617feb92013-09-09 18:09:13 -0700142 public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
143 this(context, attrs, defStyleAttr, 0, MODE_THEME);
Adam Powellfef364f2010-09-02 15:11:46 -0700144 }
145
146 /**
147 * Construct a new spinner with the given context's theme, the supplied attribute set,
148 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
149 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
150 *
151 * @param context The Context the view is running in, through which it can
152 * access the current theme, resources, etc.
153 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700154 * @param defStyleAttr An attribute in the current theme that contains a
155 * reference to a style resource that supplies default values for
156 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700157 * @param mode Constant describing how the user will select choices from the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700158 *
Adam Powellfef364f2010-09-02 15:11:46 -0700159 * @see #MODE_DIALOG
160 * @see #MODE_DROPDOWN
161 */
Alan Viverette617feb92013-09-09 18:09:13 -0700162 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
163 this(context, attrs, defStyleAttr, 0, mode);
164 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165
Alan Viverette617feb92013-09-09 18:09:13 -0700166 /**
167 * Construct a new spinner with the given context's theme, the supplied attribute set,
168 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
169 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
170 *
171 * @param context The Context the view is running in, through which it can
172 * access the current theme, resources, etc.
173 * @param attrs The attributes of the XML tag that is inflating the view.
174 * @param defStyleAttr An attribute in the current theme that contains a
175 * reference to a style resource that supplies default values for
176 * the view. Can be 0 to not look for defaults.
177 * @param defStyleRes A resource identifier of a style resource that
178 * supplies default values for the view, used only if
179 * defStyleAttr is 0 or can not be found in the theme. Can be 0
180 * to not look for defaults.
181 * @param mode Constant describing how the user will select choices from the spinner.
182 *
183 * @see #MODE_DIALOG
184 * @see #MODE_DROPDOWN
185 */
186 public Spinner(
187 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
188 super(context, attrs, defStyleAttr, defStyleRes);
189
190 final TypedArray a = context.obtainStyledAttributes(
191 attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes);
Adam Powellfef364f2010-09-02 15:11:46 -0700192
193 if (mode == MODE_THEME) {
194 mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
195 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700196
197 switch (mode) {
198 case MODE_DIALOG: {
199 mPopup = new DialogPopup();
200 break;
201 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700202
Adam Powellc3fa6302010-05-18 11:36:27 -0700203 case MODE_DROPDOWN: {
Alan Viverette617feb92013-09-09 18:09:13 -0700204 final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
Adam Powellc3fa6302010-05-18 11:36:27 -0700205
Adam Powell8db7cb12011-02-08 14:18:38 -0800206 mDropDownWidth = a.getLayoutDimension(
Ben Komalo72536f72010-10-15 11:28:31 -0700207 com.android.internal.R.styleable.Spinner_dropDownWidth,
Adam Powell8db7cb12011-02-08 14:18:38 -0800208 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700209 popup.setBackgroundDrawable(a.getDrawable(
210 com.android.internal.R.styleable.Spinner_popupBackground));
Adam Powell8132ba52011-07-15 17:37:11 -0700211 final int verticalOffset = a.getDimensionPixelOffset(
212 com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0);
213 if (verticalOffset != 0) {
214 popup.setVerticalOffset(verticalOffset);
215 }
216
217 final int horizontalOffset = a.getDimensionPixelOffset(
218 com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0);
219 if (horizontalOffset != 0) {
220 popup.setHorizontalOffset(horizontalOffset);
221 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700222
223 mPopup = popup;
Alan Viverette69960142013-08-22 17:26:57 -0700224 mForwardingListener = new ForwardingListener(this) {
Alan Viveretteca6a36112013-08-16 14:41:06 -0700225 @Override
226 public ListPopupWindow getPopup() {
227 return popup;
228 }
229
230 @Override
231 public boolean onForwardingStarted() {
232 if (!mPopup.isShowing()) {
233 mPopup.show(getTextDirection(), getTextAlignment());
234 }
235 return true;
236 }
237 };
Adam Powellc3fa6302010-05-18 11:36:27 -0700238 break;
239 }
240 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800241
Adam Powella39b9872011-01-05 16:07:54 -0800242 mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
243
Adam Powellc3fa6302010-05-18 11:36:27 -0700244 mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245
Adam Powell42b7e992011-11-08 09:47:32 -0800246 mDisableChildrenWhenDisabled = a.getBoolean(
247 com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
248
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700250
251 // Base constructor can call setAdapter before we initialize mPopup.
252 // Finish setting things up if this happened.
253 if (mTempAdapter != null) {
254 mPopup.setAdapter(mTempAdapter);
255 mTempAdapter = null;
256 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 }
Adam Powella39b9872011-01-05 16:07:54 -0800258
Adam Powelld9c7be62012-03-08 19:43:43 -0800259 /**
260 * Set the background drawable for the spinner's popup window of choices.
261 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
262 *
263 * @param background Background drawable
264 *
265 * @attr ref android.R.styleable#Spinner_popupBackground
266 */
267 public void setPopupBackgroundDrawable(Drawable background) {
268 if (!(mPopup instanceof DropdownPopup)) {
269 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
270 return;
271 }
272 ((DropdownPopup) mPopup).setBackgroundDrawable(background);
273 }
274
275 /**
276 * Set the background drawable for the spinner's popup window of choices.
277 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
278 *
Adam Powelldca510e2012-03-08 20:06:39 -0800279 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800280 *
281 * @attr ref android.R.styleable#Spinner_popupBackground
282 */
283 public void setPopupBackgroundResource(int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800284 setPopupBackgroundDrawable(getContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800285 }
286
287 /**
288 * Get the background drawable for the spinner's popup window of choices.
289 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
290 *
291 * @return background Background drawable
292 *
293 * @attr ref android.R.styleable#Spinner_popupBackground
294 */
295 public Drawable getPopupBackground() {
296 return mPopup.getBackground();
297 }
298
299 /**
300 * Set a vertical offset in pixels for the spinner's popup window of choices.
301 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
302 *
303 * @param pixels Vertical offset in pixels
304 *
305 * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
306 */
307 public void setDropDownVerticalOffset(int pixels) {
308 mPopup.setVerticalOffset(pixels);
309 }
310
311 /**
312 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
313 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
314 *
315 * @return Vertical offset in pixels
316 *
317 * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
318 */
319 public int getDropDownVerticalOffset() {
320 return mPopup.getVerticalOffset();
321 }
322
323 /**
324 * Set a horizontal offset in pixels for the spinner's popup window of choices.
325 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
326 *
327 * @param pixels Horizontal offset in pixels
328 *
329 * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
330 */
331 public void setDropDownHorizontalOffset(int pixels) {
332 mPopup.setHorizontalOffset(pixels);
333 }
334
335 /**
336 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
337 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
338 *
339 * @return Horizontal offset in pixels
340 *
341 * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
342 */
343 public int getDropDownHorizontalOffset() {
344 return mPopup.getHorizontalOffset();
345 }
346
347 /**
348 * Set the width of the spinner's popup window of choices in pixels. This value
349 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
350 * to match the width of the Spinner itself, or
351 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
352 * of contained dropdown list items.
353 *
354 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
355 *
356 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
357 *
358 * @attr ref android.R.styleable#Spinner_dropDownWidth
359 */
360 public void setDropDownWidth(int pixels) {
361 if (!(mPopup instanceof DropdownPopup)) {
362 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
363 return;
364 }
365 mDropDownWidth = pixels;
366 }
367
368 /**
369 * Get the configured width of the spinner's popup window of choices in pixels.
370 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
371 * meaning the popup window will match the width of the Spinner itself, or
372 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
373 * of contained dropdown list items.
374 *
375 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
376 *
377 * @attr ref android.R.styleable#Spinner_dropDownWidth
378 */
379 public int getDropDownWidth() {
380 return mDropDownWidth;
381 }
382
Adam Powell42b7e992011-11-08 09:47:32 -0800383 @Override
384 public void setEnabled(boolean enabled) {
385 super.setEnabled(enabled);
386 if (mDisableChildrenWhenDisabled) {
387 final int count = getChildCount();
388 for (int i = 0; i < count; i++) {
389 getChildAt(i).setEnabled(enabled);
390 }
391 }
392 }
393
Adam Powella39b9872011-01-05 16:07:54 -0800394 /**
395 * Describes how the selected item view is positioned. Currently only the horizontal component
396 * is used. The default is determined by the current theme.
397 *
398 * @param gravity See {@link android.view.Gravity}
399 *
400 * @attr ref android.R.styleable#Spinner_gravity
401 */
402 public void setGravity(int gravity) {
403 if (mGravity != gravity) {
404 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700405 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800406 }
407 mGravity = gravity;
408 requestLayout();
409 }
410 }
411
Adam Powelld9c7be62012-03-08 19:43:43 -0800412 /**
413 * Describes how the selected item view is positioned. The default is determined by the
414 * current theme.
415 *
416 * @return A {@link android.view.Gravity Gravity} value
417 */
418 public int getGravity() {
419 return mGravity;
420 }
421
Alan Viverettea089dde2013-07-24 16:38:37 -0700422 /**
423 * Sets the Adapter used to provide the data which backs this Spinner.
424 * <p>
425 * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the
426 * Adapter associated with this view. Calling
427 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
428 * returned from {@link #getAdapter()} will always return 0. Calling
429 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
430 * 1.
431 *
432 * @see AbsSpinner#setAdapter(SpinnerAdapter)
433 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700434 @Override
435 public void setAdapter(SpinnerAdapter adapter) {
436 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700437
Adam Powell6a221b32013-09-10 11:38:52 -0700438 mRecycler.clear();
439
Adam Powell68464a92010-06-07 12:48:07 -0700440 if (mPopup != null) {
441 mPopup.setAdapter(new DropDownAdapter(adapter));
442 } else {
443 mTempAdapter = new DropDownAdapter(adapter);
444 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700445 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446
447 @Override
448 public int getBaseline() {
449 View child = null;
450
451 if (getChildCount() > 0) {
452 child = getChildAt(0);
453 } else if (mAdapter != null && mAdapter.getCount() > 0) {
Adam Powell6a221b32013-09-10 11:38:52 -0700454 child = makeView(0, false);
Adam Powell22e92e52010-12-10 13:20:28 -0800455 mRecycler.put(0, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 }
457
458 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700459 final int childBaseline = child.getBaseline();
460 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 } else {
462 return -1;
463 }
464 }
465
Romain Guy5275d692009-07-15 17:00:23 -0700466 @Override
467 protected void onDetachedFromWindow() {
468 super.onDetachedFromWindow();
469
470 if (mPopup != null && mPopup.isShowing()) {
471 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700472 }
473 }
474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 /**
476 * <p>A spinner does not support item click events. Calling this method
477 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700478 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 *
480 * @param l this listener will be ignored
481 */
482 @Override
483 public void setOnItemClickListener(OnItemClickListener l) {
484 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
485 }
486
Adam Powellc4e57e22012-02-24 19:24:26 -0800487 /**
488 * @hide internal use only
489 */
490 public void setOnItemClickListenerInt(OnItemClickListener l) {
491 super.setOnItemClickListener(l);
492 }
493
Adam Powella39b9872011-01-05 16:07:54 -0800494 @Override
Alan Viveretteca6a36112013-08-16 14:41:06 -0700495 public boolean onTouchEvent(MotionEvent event) {
496 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
497 return true;
498 }
499
500 return super.onTouchEvent(event);
501 }
502
503 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800504 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
505 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
506 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
507 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800508 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800509 measureContentWidth(getAdapter(), getBackground())),
510 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800511 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800512 }
513 }
514
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 /**
516 * @see android.view.View#onLayout(boolean,int,int,int,int)
517 *
518 * Creates and positions all views
519 *
520 */
521 @Override
522 protected void onLayout(boolean changed, int l, int t, int r, int b) {
523 super.onLayout(changed, l, t, r, b);
524 mInLayout = true;
525 layout(0, false);
526 mInLayout = false;
527 }
528
529 /**
530 * Creates and positions all views for this Spinner.
531 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700532 * @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 -0800533 * so views are scrolling to the left. -1 means selection is moving to the left.
534 */
535 @Override
536 void layout(int delta, boolean animate) {
537 int childrenLeft = mSpinnerPadding.left;
538 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
539
540 if (mDataChanged) {
541 handleDataChanged();
542 }
543
544 // Handle the empty set by removing all views
545 if (mItemCount == 0) {
546 resetList();
547 return;
548 }
549
550 if (mNextSelectedPosition >= 0) {
551 setSelectedPositionInt(mNextSelectedPosition);
552 }
553
554 recycleAllViews();
555
556 // Clear out old views
557 removeAllViewsInLayout();
558
Adam Powella39b9872011-01-05 16:07:54 -0800559 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700561
562 if (mAdapter != null) {
Adam Powell6a221b32013-09-10 11:38:52 -0700563 View sel = makeView(mSelectedPosition, true);
Adam Powell72d574c2013-04-10 11:27:08 -0700564 int width = sel.getMeasuredWidth();
565 int selectedOffset = childrenLeft;
566 final int layoutDirection = getLayoutDirection();
567 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
568 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
569 case Gravity.CENTER_HORIZONTAL:
570 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
571 break;
572 case Gravity.RIGHT:
573 selectedOffset = childrenLeft + childrenWidth - width;
574 break;
575 }
576 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800577 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578
579 // Flush any cached views that did not get reused above
580 mRecycler.clear();
581
582 invalidate();
583
584 checkSelectionChanged();
585
586 mDataChanged = false;
587 mNeedSync = false;
588 setNextSelectedPositionInt(mSelectedPosition);
589 }
590
591 /**
592 * Obtain a view, either by pulling an existing view from the recycler or
593 * by getting a new one from the adapter. If we are animating, make sure
594 * there is enough information in the view's layout parameters to animate
595 * from the old to new positions.
596 *
597 * @param position Position in the spinner for the view to obtain
Adam Powell6a221b32013-09-10 11:38:52 -0700598 * @param addChild true to add the child to the spinner, false to obtain and configure only.
599 * @return A view for the given position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800600 */
Adam Powell6a221b32013-09-10 11:38:52 -0700601 private View makeView(int position, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 View child;
603
604 if (!mDataChanged) {
605 child = mRecycler.get(position);
606 if (child != null) {
607 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700608 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609
610 return child;
611 }
612 }
613
614 // Nothing found in the recycler -- ask the adapter for a view
615 child = mAdapter.getView(position, null, this);
616
617 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700618 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619
620 return child;
621 }
622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 /**
624 * Helper for makeAndAddView to set the position of a view
625 * and fill out its layout paramters.
626 *
627 * @param child The view to position
Adam Powell6a221b32013-09-10 11:38:52 -0700628 * @param addChild true if the child should be added to the Spinner during setup
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 */
Adam Powell6a221b32013-09-10 11:38:52 -0700630 private void setUpChild(View child, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631
632 // Respect layout params that are already in the view. Otherwise
633 // make some up...
634 ViewGroup.LayoutParams lp = child.getLayoutParams();
635 if (lp == null) {
636 lp = generateDefaultLayoutParams();
637 }
638
Adam Powell6a221b32013-09-10 11:38:52 -0700639 if (addChild) {
640 addViewInLayout(child, 0, lp);
641 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642
643 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800644 if (mDisableChildrenWhenDisabled) {
645 child.setEnabled(isEnabled());
646 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647
648 // Get measure specs
649 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
650 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
651 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
652 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
653
654 // Measure child
655 child.measure(childWidthSpec, childHeightSpec);
656
657 int childLeft;
658 int childRight;
659
660 // Position vertically based on gravity setting
661 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800662 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800663 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
664 int childBottom = childTop + child.getMeasuredHeight();
665
666 int width = child.getMeasuredWidth();
667 childLeft = 0;
668 childRight = childLeft + width;
669
670 child.layout(childLeft, childTop, childRight, childBottom);
671 }
672
673 @Override
674 public boolean performClick() {
675 boolean handled = super.performClick();
676
677 if (!handled) {
678 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679
Adam Powellc3fa6302010-05-18 11:36:27 -0700680 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800681 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 }
684
685 return handled;
686 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800687
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 public void onClick(DialogInterface dialog, int which) {
689 setSelection(which);
690 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 }
692
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800693 @Override
694 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
695 super.onInitializeAccessibilityEvent(event);
696 event.setClassName(Spinner.class.getName());
697 }
698
699 @Override
700 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
701 super.onInitializeAccessibilityNodeInfo(info);
702 info.setClassName(Spinner.class.getName());
Alan Viverette058ac7c2013-08-19 16:44:30 -0700703
704 if (mAdapter != null) {
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700705 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700706 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800707 }
708
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 /**
710 * Sets the prompt to display when the dialog is shown.
711 * @param prompt the prompt to set
712 */
713 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700714 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715 }
716
717 /**
718 * Sets the prompt to display when the dialog is shown.
719 * @param promptId the resource ID of the prompt to display when the dialog is shown
720 */
721 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700722 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 }
724
725 /**
726 * @return The prompt to display when the dialog is shown
727 */
728 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700729 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
Adam Powell19fd1642011-02-07 19:00:11 -0800731
Adam Powellb70c7272011-02-10 12:04:59 -0800732 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800733 if (adapter == null) {
734 return 0;
735 }
736
737 int width = 0;
738 View itemView = null;
739 int itemType = 0;
740 final int widthMeasureSpec =
741 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
742 final int heightMeasureSpec =
743 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
744
745 // Make sure the number of items we'll measure is capped. If it's a huge data set
746 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800747 int start = Math.max(0, getSelectedItemPosition());
748 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
749 final int count = end - start;
750 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
751 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800752 final int positionType = adapter.getItemViewType(i);
753 if (positionType != itemType) {
754 itemType = positionType;
755 itemView = null;
756 }
757 itemView = adapter.getView(i, itemView, this);
758 if (itemView.getLayoutParams() == null) {
759 itemView.setLayoutParams(new ViewGroup.LayoutParams(
760 ViewGroup.LayoutParams.WRAP_CONTENT,
761 ViewGroup.LayoutParams.WRAP_CONTENT));
762 }
763 itemView.measure(widthMeasureSpec, heightMeasureSpec);
764 width = Math.max(width, itemView.getMeasuredWidth());
765 }
766
767 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800768 if (background != null) {
769 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800770 width += mTempRect.left + mTempRect.right;
771 }
772
773 return width;
774 }
775
Adam Powell235ae5f2012-12-10 13:38:03 -0800776 @Override
777 public Parcelable onSaveInstanceState() {
778 final SavedState ss = new SavedState(super.onSaveInstanceState());
779 ss.showDropdown = mPopup != null && mPopup.isShowing();
780 return ss;
781 }
782
783 @Override
784 public void onRestoreInstanceState(Parcelable state) {
785 SavedState ss = (SavedState) state;
786
787 super.onRestoreInstanceState(ss.getSuperState());
788
789 if (ss.showDropdown) {
790 ViewTreeObserver vto = getViewTreeObserver();
791 if (vto != null) {
792 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
793 @Override
794 public void onGlobalLayout() {
795 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800796 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800797 }
798 final ViewTreeObserver vto = getViewTreeObserver();
799 if (vto != null) {
800 vto.removeOnGlobalLayoutListener(this);
801 }
802 }
803 };
804 vto.addOnGlobalLayoutListener(listener);
805 }
806 }
807 }
808
809 static class SavedState extends AbsSpinner.SavedState {
810 boolean showDropdown;
811
812 SavedState(Parcelable superState) {
813 super(superState);
814 }
815
816 private SavedState(Parcel in) {
817 super(in);
818 showDropdown = in.readByte() != 0;
819 }
820
821 @Override
822 public void writeToParcel(Parcel out, int flags) {
823 super.writeToParcel(out, flags);
824 out.writeByte((byte) (showDropdown ? 1 : 0));
825 }
826
827 public static final Parcelable.Creator<SavedState> CREATOR =
828 new Parcelable.Creator<SavedState>() {
829 public SavedState createFromParcel(Parcel in) {
830 return new SavedState(in);
831 }
832
833 public SavedState[] newArray(int size) {
834 return new SavedState[size];
835 }
836 };
837 }
838
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 /**
840 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
841 * into a ListAdapter.</p>
842 */
843 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
844 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800845 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846
847 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800848 * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 *
850 * @param adapter the Adapter to transform into a ListAdapter
851 */
852 public DropDownAdapter(SpinnerAdapter adapter) {
853 this.mAdapter = adapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800854 if (adapter instanceof ListAdapter) {
855 this.mListAdapter = (ListAdapter) adapter;
856 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 }
858
859 public int getCount() {
860 return mAdapter == null ? 0 : mAdapter.getCount();
861 }
862
863 public Object getItem(int position) {
864 return mAdapter == null ? null : mAdapter.getItem(position);
865 }
866
867 public long getItemId(int position) {
868 return mAdapter == null ? -1 : mAdapter.getItemId(position);
869 }
870
871 public View getView(int position, View convertView, ViewGroup parent) {
872 return getDropDownView(position, convertView, parent);
873 }
874
875 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800876 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 }
878
879 public boolean hasStableIds() {
880 return mAdapter != null && mAdapter.hasStableIds();
881 }
882
883 public void registerDataSetObserver(DataSetObserver observer) {
884 if (mAdapter != null) {
885 mAdapter.registerDataSetObserver(observer);
886 }
887 }
888
889 public void unregisterDataSetObserver(DataSetObserver observer) {
890 if (mAdapter != null) {
891 mAdapter.unregisterDataSetObserver(observer);
892 }
893 }
894
895 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800896 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
897 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 */
899 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -0800900 final ListAdapter adapter = mListAdapter;
901 if (adapter != null) {
902 return adapter.areAllItemsEnabled();
903 } else {
904 return true;
905 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 }
907
908 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800909 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
910 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 */
912 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -0800913 final ListAdapter adapter = mListAdapter;
914 if (adapter != null) {
915 return adapter.isEnabled(position);
916 } else {
917 return true;
918 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 }
920
921 public int getItemViewType(int position) {
922 return 0;
923 }
924
925 public int getViewTypeCount() {
926 return 1;
927 }
928
929 public boolean isEmpty() {
930 return getCount() == 0;
931 }
932 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700933
934 /**
935 * Implements some sort of popup selection interface for selecting a spinner option.
936 * Allows for different spinner modes.
937 */
938 private interface SpinnerPopup {
939 public void setAdapter(ListAdapter adapter);
940
941 /**
942 * Show the popup
943 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800944 public void show(int textDirection, int textAlignment);
945
Adam Powellc3fa6302010-05-18 11:36:27 -0700946 /**
947 * Dismiss the popup
948 */
949 public void dismiss();
950
951 /**
952 * @return true if the popup is showing, false otherwise.
953 */
954 public boolean isShowing();
955
956 /**
957 * Set hint text to be displayed to the user. This should provide
958 * a description of the choice being made.
959 * @param hintText Hint text to set.
960 */
961 public void setPromptText(CharSequence hintText);
962 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -0800963
964 public void setBackgroundDrawable(Drawable bg);
965 public void setVerticalOffset(int px);
966 public void setHorizontalOffset(int px);
967 public Drawable getBackground();
968 public int getVerticalOffset();
969 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -0700970 }
971
972 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
973 private AlertDialog mPopup;
974 private ListAdapter mListAdapter;
975 private CharSequence mPrompt;
976
977 public void dismiss() {
Daniel 2 Olofsson2f77f9c2013-06-10 14:49:14 +0200978 if (mPopup != null) {
979 mPopup.dismiss();
980 mPopup = null;
981 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700982 }
983
984 public boolean isShowing() {
985 return mPopup != null ? mPopup.isShowing() : false;
986 }
987
988 public void setAdapter(ListAdapter adapter) {
989 mListAdapter = adapter;
990 }
991
992 public void setPromptText(CharSequence hintText) {
993 mPrompt = hintText;
994 }
995
996 public CharSequence getHintText() {
997 return mPrompt;
998 }
999
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001000 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -07001001 if (mListAdapter == null) {
1002 return;
1003 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001004 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
1005 if (mPrompt != null) {
1006 builder.setTitle(mPrompt);
1007 }
1008 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001009 getSelectedItemPosition(), this).create();
1010 final ListView listView = mPopup.getListView();
1011 listView.setTextDirection(textDirection);
1012 listView.setTextAlignment(textAlignment);
1013 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -07001014 }
1015
1016 public void onClick(DialogInterface dialog, int which) {
1017 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -08001018 if (mOnItemClickListener != null) {
1019 performItemClick(null, which, mListAdapter.getItemId(which));
1020 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001021 dismiss();
1022 }
Adam Powelld9c7be62012-03-08 19:43:43 -08001023
1024 @Override
1025 public void setBackgroundDrawable(Drawable bg) {
1026 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1027 }
1028
1029 @Override
1030 public void setVerticalOffset(int px) {
1031 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1032 }
1033
1034 @Override
1035 public void setHorizontalOffset(int px) {
1036 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1037 }
1038
1039 @Override
1040 public Drawable getBackground() {
1041 return null;
1042 }
1043
1044 @Override
1045 public int getVerticalOffset() {
1046 return 0;
1047 }
1048
1049 @Override
1050 public int getHorizontalOffset() {
1051 return 0;
1052 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001053 }
1054
1055 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1056 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001057 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001058
Alan Viverette617feb92013-09-09 18:09:13 -07001059 public DropdownPopup(
1060 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1061 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001062
Adam Powellc3fa6302010-05-18 11:36:27 -07001063 setAnchorView(Spinner.this);
1064 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001065 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001066 setOnItemClickListener(new OnItemClickListener() {
1067 public void onItemClick(AdapterView parent, View v, int position, long id) {
1068 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001069 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001070 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001071 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001072 dismiss();
1073 }
1074 });
1075 }
1076
Adam Powell19fd1642011-02-07 19:00:11 -08001077 @Override
1078 public void setAdapter(ListAdapter adapter) {
1079 super.setAdapter(adapter);
1080 mAdapter = adapter;
1081 }
1082
Adam Powellc3fa6302010-05-18 11:36:27 -07001083 public CharSequence getHintText() {
1084 return mHintText;
1085 }
1086
1087 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001088 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001089 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001090 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001091
Adam Powell235ae5f2012-12-10 13:38:03 -08001092 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001093 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001094 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001095 if (background != null) {
1096 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001097 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001098 } else {
1099 mTempRect.left = mTempRect.right = 0;
1100 }
1101
Adam Powell62e2bde2011-08-15 15:50:05 -07001102 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001103 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1104 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001105
Adam Powell8db7cb12011-02-08 14:18:38 -08001106 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001107 int contentWidth = measureContentWidth(
1108 (SpinnerAdapter) mAdapter, getBackground());
1109 final int contentWidthLimit = mContext.getResources()
1110 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1111 if (contentWidth > contentWidthLimit) {
1112 contentWidth = contentWidthLimit;
1113 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001114 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001115 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001116 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001117 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001118 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001119 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001120 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001121
1122 if (isLayoutRtl()) {
1123 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1124 } else {
1125 hOffset += spinnerPaddingLeft;
1126 }
1127 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001128 }
1129
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001130 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001131 final boolean wasShowing = isShowing();
1132
1133 computeContentWidth();
1134
Adam Powell6f5e9342011-01-27 13:30:55 -08001135 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001136 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001137 final ListView listView = getListView();
1138 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1139 listView.setTextDirection(textDirection);
1140 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001141 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001142
Adam Powell235ae5f2012-12-10 13:38:03 -08001143 if (wasShowing) {
1144 // Skip setting up the layout/dismiss listener below. If we were previously
1145 // showing it will still stick around.
1146 return;
1147 }
1148
Adam Powellf16daf62012-10-03 11:51:34 -07001149 // Make sure we hide if our anchor goes away.
1150 // TODO: This might be appropriate to push all the way down to PopupWindow,
1151 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1152 final ViewTreeObserver vto = getViewTreeObserver();
1153 if (vto != null) {
1154 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1155 @Override
1156 public void onGlobalLayout() {
1157 if (!Spinner.this.isVisibleToUser()) {
1158 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001159 } else {
1160 computeContentWidth();
1161
1162 // Use super.show here to update; we don't want to move the selected
1163 // position or adjust other things that would be reset otherwise.
1164 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001165 }
1166 }
1167 };
1168 vto.addOnGlobalLayoutListener(layoutListener);
1169 setOnDismissListener(new OnDismissListener() {
1170 @Override public void onDismiss() {
1171 final ViewTreeObserver vto = getViewTreeObserver();
1172 if (vto != null) {
1173 vto.removeOnGlobalLayoutListener(layoutListener);
1174 }
1175 }
1176 });
1177 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001178 }
1179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180}