blob: 0d76239f29099341e0b9754d96964876bac6a114 [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;
Alan Viveretted5269772014-06-17 16:43:45 -070028import android.os.Build;
Adam Powell235ae5f2012-12-10 13:38:03 -080029import android.os.Parcel;
30import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.util.AttributeSet;
Adam Powelld9c7be62012-03-08 19:43:43 -080032import android.util.Log;
Adam Powella39b9872011-01-05 16:07:54 -080033import android.view.Gravity;
Alan Viveretteca6a3612013-08-16 14:41:06 -070034import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.view.View;
36import android.view.ViewGroup;
Adam Powellf16daf62012-10-03 11:51:34 -070037import android.view.ViewTreeObserver;
38import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080039import android.view.accessibility.AccessibilityEvent;
40import android.view.accessibility.AccessibilityNodeInfo;
Alan Viveretteca6a3612013-08-16 14:41:06 -070041import android.widget.ListPopupWindow.ForwardingListener;
Adam Powellf16daf62012-10-03 11:51:34 -070042import android.widget.PopupWindow.OnDismissListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043
44
45/**
46 * A view that displays one child at a time and lets the user pick among them.
47 * The items in the Spinner come from the {@link Adapter} associated with
48 * this view.
Scott Main41ec6532010-08-19 16:57:07 -070049 *
Scott Main4c359b72012-07-24 15:51:27 -070050 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
SeongJae Park95148492012-02-29 01:56:43 +090051 *
Scott Main4c359b72012-07-24 15:51:27 -070052 * @attr ref android.R.styleable#Spinner_dropDownSelector
Scott Main4c359b72012-07-24 15:51:27 -070053 * @attr ref android.R.styleable#Spinner_dropDownWidth
54 * @attr ref android.R.styleable#Spinner_gravity
55 * @attr ref android.R.styleable#Spinner_popupBackground
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 * @attr ref android.R.styleable#Spinner_prompt
Scott Main4c359b72012-07-24 15:51:27 -070057 * @attr ref android.R.styleable#Spinner_spinnerMode
Alan Viverette40ce0702014-08-28 15:27:04 -070058 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
59 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 */
61@Widget
62public class Spinner extends AbsSpinner implements OnClickListener {
Adam Powellc3fa6302010-05-18 11:36:27 -070063 private static final String TAG = "Spinner";
SeongJae Park95148492012-02-29 01:56:43 +090064
Adam Powell50f784c2010-12-19 16:12:19 -080065 // Only measure this many items to get a decent max width.
66 private static final int MAX_ITEMS_MEASURED = 15;
67
Adam Powellc3fa6302010-05-18 11:36:27 -070068 /**
69 * Use a dialog window for selecting spinner options.
70 */
71 public static final int MODE_DIALOG = 0;
SeongJae Park95148492012-02-29 01:56:43 +090072
Adam Powellc3fa6302010-05-18 11:36:27 -070073 /**
74 * Use a dropdown anchored to the Spinner for selecting spinner options.
75 */
76 public static final int MODE_DROPDOWN = 1;
77
Adam Powellfef364f2010-09-02 15:11:46 -070078 /**
79 * Use the theme-supplied value to select the dropdown mode.
80 */
81 private static final int MODE_THEME = -1;
Alan Viveretteca6a3612013-08-16 14:41:06 -070082
83 /** Forwarding listener used to implement drag-to-open. */
84 private ForwardingListener mForwardingListener;
85
Adam Powellc3fa6302010-05-18 11:36:27 -070086 private SpinnerPopup mPopup;
Adam Powell68464a92010-06-07 12:48:07 -070087 private DropDownAdapter mTempAdapter;
Adam Powell8db7cb12011-02-08 14:18:38 -080088 int mDropDownWidth;
Adam Powellfef364f2010-09-02 15:11:46 -070089
Adam Powella39b9872011-01-05 16:07:54 -080090 private int mGravity;
Adam Powell42b7e992011-11-08 09:47:32 -080091 private boolean mDisableChildrenWhenDisabled;
Adam Powella39b9872011-01-05 16:07:54 -080092
Adam Powell19fd1642011-02-07 19:00:11 -080093 private Rect mTempRect = new Rect();
94
Adam Powellfef364f2010-09-02 15:11:46 -070095 /**
96 * Construct a new spinner with the given context's theme.
97 *
98 * @param context The Context the view is running in, through which it can
99 * access the current theme, resources, etc.
100 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 public Spinner(Context context) {
102 this(context, null);
103 }
104
Adam Powellfef364f2010-09-02 15:11:46 -0700105 /**
106 * Construct a new spinner with the given context's theme and the supplied
107 * mode of displaying choices. <code>mode</code> may be one of
108 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
109 *
110 * @param context The Context the view is running in, through which it can
111 * access the current theme, resources, etc.
112 * @param mode Constant describing how the user will select choices from the spinner.
113 *
114 * @see #MODE_DIALOG
115 * @see #MODE_DROPDOWN
116 */
117 public Spinner(Context context, int mode) {
118 this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
119 }
120
121 /**
122 * Construct a new spinner with the given context's theme and the supplied attribute set.
123 *
124 * @param context The Context the view is running in, through which it can
125 * access the current theme, resources, etc.
126 * @param attrs The attributes of the XML tag that is inflating the view.
127 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 public Spinner(Context context, AttributeSet attrs) {
129 this(context, attrs, com.android.internal.R.attr.spinnerStyle);
130 }
131
Adam Powellfef364f2010-09-02 15:11:46 -0700132 /**
133 * Construct a new spinner with the given context's theme, the supplied attribute set,
Alan Viverette617feb92013-09-09 18:09:13 -0700134 * and default style attribute.
Adam Powellfef364f2010-09-02 15:11:46 -0700135 *
136 * @param context The Context the view is running in, through which it can
137 * access the current theme, resources, etc.
138 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700139 * @param defStyleAttr An attribute in the current theme that contains a
140 * reference to a style resource that supplies default values for
141 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700142 */
Alan Viverette617feb92013-09-09 18:09:13 -0700143 public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
144 this(context, attrs, defStyleAttr, 0, MODE_THEME);
Adam Powellfef364f2010-09-02 15:11:46 -0700145 }
146
147 /**
148 * Construct a new spinner with the given context's theme, the supplied attribute set,
149 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
150 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
151 *
152 * @param context The Context the view is running in, through which it can
153 * access the current theme, resources, etc.
154 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700155 * @param defStyleAttr An attribute in the current theme that contains a
156 * reference to a style resource that supplies default values for
157 * the view. Can be 0 to not look for defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700158 * @param mode Constant describing how the user will select choices from the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700159 *
Adam Powellfef364f2010-09-02 15:11:46 -0700160 * @see #MODE_DIALOG
161 * @see #MODE_DROPDOWN
162 */
Alan Viverette617feb92013-09-09 18:09:13 -0700163 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
164 this(context, attrs, defStyleAttr, 0, mode);
165 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166
Alan Viverette617feb92013-09-09 18:09:13 -0700167 /**
168 * Construct a new spinner with the given context's theme, the supplied attribute set,
169 * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
170 * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
171 *
172 * @param context The Context the view is running in, through which it can
173 * access the current theme, resources, etc.
174 * @param attrs The attributes of the XML tag that is inflating the view.
175 * @param defStyleAttr An attribute in the current theme that contains a
176 * reference to a style resource that supplies default values for
177 * the view. Can be 0 to not look for defaults.
178 * @param defStyleRes A resource identifier of a style resource that
179 * supplies default values for the view, used only if
180 * defStyleAttr is 0 or can not be found in the theme. Can be 0
181 * to not look for defaults.
182 * @param mode Constant describing how the user will select choices from the spinner.
183 *
184 * @see #MODE_DIALOG
185 * @see #MODE_DROPDOWN
186 */
187 public Spinner(
188 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
189 super(context, attrs, defStyleAttr, defStyleRes);
190
191 final TypedArray a = context.obtainStyledAttributes(
192 attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes);
Adam Powellfef364f2010-09-02 15:11:46 -0700193
194 if (mode == MODE_THEME) {
195 mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
196 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700197
198 switch (mode) {
199 case MODE_DIALOG: {
200 mPopup = new DialogPopup();
201 break;
202 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700203
Adam Powellc3fa6302010-05-18 11:36:27 -0700204 case MODE_DROPDOWN: {
Alan Viverette617feb92013-09-09 18:09:13 -0700205 final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
Adam Powellc3fa6302010-05-18 11:36:27 -0700206
Adam Powell8db7cb12011-02-08 14:18:38 -0800207 mDropDownWidth = a.getLayoutDimension(
Ben Komalo72536f72010-10-15 11:28:31 -0700208 com.android.internal.R.styleable.Spinner_dropDownWidth,
Adam Powell8db7cb12011-02-08 14:18:38 -0800209 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700210 popup.setBackgroundDrawable(a.getDrawable(
211 com.android.internal.R.styleable.Spinner_popupBackground));
Adam Powellc3fa6302010-05-18 11:36:27 -0700212
213 mPopup = popup;
Alan Viverette69960142013-08-22 17:26:57 -0700214 mForwardingListener = new ForwardingListener(this) {
Alan Viveretteca6a3612013-08-16 14:41:06 -0700215 @Override
216 public ListPopupWindow getPopup() {
217 return popup;
218 }
219
220 @Override
221 public boolean onForwardingStarted() {
222 if (!mPopup.isShowing()) {
223 mPopup.show(getTextDirection(), getTextAlignment());
224 }
225 return true;
226 }
227 };
Adam Powellc3fa6302010-05-18 11:36:27 -0700228 break;
229 }
230 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800231
Adam Powella39b9872011-01-05 16:07:54 -0800232 mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
233
Adam Powellc3fa6302010-05-18 11:36:27 -0700234 mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
Adam Powell42b7e992011-11-08 09:47:32 -0800236 mDisableChildrenWhenDisabled = a.getBoolean(
237 com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
238
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700240
241 // Base constructor can call setAdapter before we initialize mPopup.
242 // Finish setting things up if this happened.
243 if (mTempAdapter != null) {
244 mPopup.setAdapter(mTempAdapter);
245 mTempAdapter = null;
246 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 }
Adam Powella39b9872011-01-05 16:07:54 -0800248
Adam Powelld9c7be62012-03-08 19:43:43 -0800249 /**
250 * Set the background drawable for the spinner's popup window of choices.
251 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
252 *
253 * @param background Background drawable
254 *
255 * @attr ref android.R.styleable#Spinner_popupBackground
256 */
257 public void setPopupBackgroundDrawable(Drawable background) {
258 if (!(mPopup instanceof DropdownPopup)) {
259 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
260 return;
261 }
262 ((DropdownPopup) mPopup).setBackgroundDrawable(background);
263 }
264
265 /**
266 * Set the background drawable for the spinner's popup window of choices.
267 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
268 *
Adam Powelldca510e2012-03-08 20:06:39 -0800269 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800270 *
271 * @attr ref android.R.styleable#Spinner_popupBackground
272 */
273 public void setPopupBackgroundResource(int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800274 setPopupBackgroundDrawable(getContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800275 }
276
277 /**
278 * Get the background drawable for the spinner's popup window of choices.
279 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
280 *
281 * @return background Background drawable
282 *
283 * @attr ref android.R.styleable#Spinner_popupBackground
284 */
285 public Drawable getPopupBackground() {
286 return mPopup.getBackground();
287 }
288
289 /**
290 * Set a vertical offset in pixels for the spinner's popup window of choices.
291 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
292 *
293 * @param pixels Vertical offset in pixels
294 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700295 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800296 */
297 public void setDropDownVerticalOffset(int pixels) {
298 mPopup.setVerticalOffset(pixels);
299 }
300
301 /**
302 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
303 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
304 *
305 * @return Vertical offset in pixels
306 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700307 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800308 */
309 public int getDropDownVerticalOffset() {
310 return mPopup.getVerticalOffset();
311 }
312
313 /**
314 * Set a horizontal offset in pixels for the spinner's popup window of choices.
315 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
316 *
317 * @param pixels Horizontal offset in pixels
318 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700319 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800320 */
321 public void setDropDownHorizontalOffset(int pixels) {
322 mPopup.setHorizontalOffset(pixels);
323 }
324
325 /**
326 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
327 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
328 *
329 * @return Horizontal offset in pixels
330 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700331 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800332 */
333 public int getDropDownHorizontalOffset() {
334 return mPopup.getHorizontalOffset();
335 }
336
337 /**
338 * Set the width of the spinner's popup window of choices in pixels. This value
339 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
340 * to match the width of the Spinner itself, or
341 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
342 * of contained dropdown list items.
343 *
344 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
345 *
346 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
347 *
348 * @attr ref android.R.styleable#Spinner_dropDownWidth
349 */
350 public void setDropDownWidth(int pixels) {
351 if (!(mPopup instanceof DropdownPopup)) {
352 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
353 return;
354 }
355 mDropDownWidth = pixels;
356 }
357
358 /**
359 * Get the configured width of the spinner's popup window of choices in pixels.
360 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
361 * meaning the popup window will match the width of the Spinner itself, or
362 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
363 * of contained dropdown list items.
364 *
365 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
366 *
367 * @attr ref android.R.styleable#Spinner_dropDownWidth
368 */
369 public int getDropDownWidth() {
370 return mDropDownWidth;
371 }
372
Adam Powell42b7e992011-11-08 09:47:32 -0800373 @Override
374 public void setEnabled(boolean enabled) {
375 super.setEnabled(enabled);
376 if (mDisableChildrenWhenDisabled) {
377 final int count = getChildCount();
378 for (int i = 0; i < count; i++) {
379 getChildAt(i).setEnabled(enabled);
380 }
381 }
382 }
383
Adam Powella39b9872011-01-05 16:07:54 -0800384 /**
385 * Describes how the selected item view is positioned. Currently only the horizontal component
386 * is used. The default is determined by the current theme.
387 *
388 * @param gravity See {@link android.view.Gravity}
389 *
390 * @attr ref android.R.styleable#Spinner_gravity
391 */
392 public void setGravity(int gravity) {
393 if (mGravity != gravity) {
394 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700395 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800396 }
397 mGravity = gravity;
398 requestLayout();
399 }
400 }
401
Adam Powelld9c7be62012-03-08 19:43:43 -0800402 /**
403 * Describes how the selected item view is positioned. The default is determined by the
404 * current theme.
405 *
406 * @return A {@link android.view.Gravity Gravity} value
407 */
408 public int getGravity() {
409 return mGravity;
410 }
411
Alan Viverettea089dde2013-07-24 16:38:37 -0700412 /**
413 * Sets the Adapter used to provide the data which backs this Spinner.
414 * <p>
415 * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the
416 * Adapter associated with this view. Calling
417 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
418 * returned from {@link #getAdapter()} will always return 0. Calling
419 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700420 * 1. On API {@link Build.VERSION_CODES#LOLLIPOP} and above, attempting to set an
Alan Viveretted5269772014-06-17 16:43:45 -0700421 * adapter with more than one view type will throw an
422 * {@link IllegalArgumentException}.
423 *
424 * @param adapter the adapter to set
Alan Viverettea089dde2013-07-24 16:38:37 -0700425 *
426 * @see AbsSpinner#setAdapter(SpinnerAdapter)
Alan Viveretted5269772014-06-17 16:43:45 -0700427 * @throws IllegalArgumentException if the adapter has more than one view
428 * type
Alan Viverettea089dde2013-07-24 16:38:37 -0700429 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700430 @Override
431 public void setAdapter(SpinnerAdapter adapter) {
432 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700433
Adam Powell6a221b32013-09-10 11:38:52 -0700434 mRecycler.clear();
435
Alan Viveretted5269772014-06-17 16:43:45 -0700436 final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700437 if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
Alan Viveretted5269772014-06-17 16:43:45 -0700438 && adapter != null && adapter.getViewTypeCount() != 1) {
439 throw new IllegalArgumentException("Spinner adapter view type count must be 1");
440 }
441
Adam Powell68464a92010-06-07 12:48:07 -0700442 if (mPopup != null) {
443 mPopup.setAdapter(new DropDownAdapter(adapter));
444 } else {
445 mTempAdapter = new DropDownAdapter(adapter);
446 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448
449 @Override
450 public int getBaseline() {
451 View child = null;
452
453 if (getChildCount() > 0) {
454 child = getChildAt(0);
455 } else if (mAdapter != null && mAdapter.getCount() > 0) {
Adam Powell6a221b32013-09-10 11:38:52 -0700456 child = makeView(0, false);
Adam Powell22e92e52010-12-10 13:20:28 -0800457 mRecycler.put(0, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 }
459
460 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700461 final int childBaseline = child.getBaseline();
462 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 } else {
464 return -1;
465 }
466 }
467
Romain Guy5275d692009-07-15 17:00:23 -0700468 @Override
469 protected void onDetachedFromWindow() {
470 super.onDetachedFromWindow();
471
472 if (mPopup != null && mPopup.isShowing()) {
473 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700474 }
475 }
476
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 /**
478 * <p>A spinner does not support item click events. Calling this method
479 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700480 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 *
482 * @param l this listener will be ignored
483 */
484 @Override
485 public void setOnItemClickListener(OnItemClickListener l) {
486 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
487 }
488
Adam Powellc4e57e22012-02-24 19:24:26 -0800489 /**
490 * @hide internal use only
491 */
492 public void setOnItemClickListenerInt(OnItemClickListener l) {
493 super.setOnItemClickListener(l);
494 }
495
Adam Powella39b9872011-01-05 16:07:54 -0800496 @Override
Alan Viveretteca6a3612013-08-16 14:41:06 -0700497 public boolean onTouchEvent(MotionEvent event) {
498 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
499 return true;
500 }
501
502 return super.onTouchEvent(event);
503 }
504
505 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800506 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
507 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
508 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
509 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800510 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800511 measureContentWidth(getAdapter(), getBackground())),
512 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800513 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800514 }
515 }
516
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 /**
518 * @see android.view.View#onLayout(boolean,int,int,int,int)
519 *
520 * Creates and positions all views
521 *
522 */
523 @Override
524 protected void onLayout(boolean changed, int l, int t, int r, int b) {
525 super.onLayout(changed, l, t, r, b);
526 mInLayout = true;
527 layout(0, false);
528 mInLayout = false;
529 }
530
531 /**
532 * Creates and positions all views for this Spinner.
533 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700534 * @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 -0800535 * so views are scrolling to the left. -1 means selection is moving to the left.
536 */
537 @Override
538 void layout(int delta, boolean animate) {
539 int childrenLeft = mSpinnerPadding.left;
540 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
541
542 if (mDataChanged) {
543 handleDataChanged();
544 }
545
546 // Handle the empty set by removing all views
547 if (mItemCount == 0) {
548 resetList();
549 return;
550 }
551
552 if (mNextSelectedPosition >= 0) {
553 setSelectedPositionInt(mNextSelectedPosition);
554 }
555
556 recycleAllViews();
557
558 // Clear out old views
559 removeAllViewsInLayout();
560
Adam Powella39b9872011-01-05 16:07:54 -0800561 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700563
564 if (mAdapter != null) {
Adam Powell6a221b32013-09-10 11:38:52 -0700565 View sel = makeView(mSelectedPosition, true);
Adam Powell72d574c2013-04-10 11:27:08 -0700566 int width = sel.getMeasuredWidth();
567 int selectedOffset = childrenLeft;
568 final int layoutDirection = getLayoutDirection();
569 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
570 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
571 case Gravity.CENTER_HORIZONTAL:
572 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
573 break;
574 case Gravity.RIGHT:
575 selectedOffset = childrenLeft + childrenWidth - width;
576 break;
577 }
578 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800580
581 // Flush any cached views that did not get reused above
582 mRecycler.clear();
583
584 invalidate();
585
586 checkSelectionChanged();
587
588 mDataChanged = false;
589 mNeedSync = false;
590 setNextSelectedPositionInt(mSelectedPosition);
591 }
592
593 /**
594 * Obtain a view, either by pulling an existing view from the recycler or
595 * by getting a new one from the adapter. If we are animating, make sure
596 * there is enough information in the view's layout parameters to animate
597 * from the old to new positions.
598 *
599 * @param position Position in the spinner for the view to obtain
Adam Powell6a221b32013-09-10 11:38:52 -0700600 * @param addChild true to add the child to the spinner, false to obtain and configure only.
601 * @return A view for the given position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 */
Adam Powell6a221b32013-09-10 11:38:52 -0700603 private View makeView(int position, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 View child;
605
606 if (!mDataChanged) {
607 child = mRecycler.get(position);
608 if (child != null) {
609 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700610 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611
612 return child;
613 }
614 }
615
616 // Nothing found in the recycler -- ask the adapter for a view
617 child = mAdapter.getView(position, null, this);
618
619 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700620 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621
622 return child;
623 }
624
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 /**
626 * Helper for makeAndAddView to set the position of a view
627 * and fill out its layout paramters.
628 *
629 * @param child The view to position
Adam Powell6a221b32013-09-10 11:38:52 -0700630 * @param addChild true if the child should be added to the Spinner during setup
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631 */
Adam Powell6a221b32013-09-10 11:38:52 -0700632 private void setUpChild(View child, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800633
634 // Respect layout params that are already in the view. Otherwise
635 // make some up...
636 ViewGroup.LayoutParams lp = child.getLayoutParams();
637 if (lp == null) {
638 lp = generateDefaultLayoutParams();
639 }
640
Adam Powell6a221b32013-09-10 11:38:52 -0700641 if (addChild) {
642 addViewInLayout(child, 0, lp);
643 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800644
645 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800646 if (mDisableChildrenWhenDisabled) {
647 child.setEnabled(isEnabled());
648 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649
650 // Get measure specs
651 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
652 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
653 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
654 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
655
656 // Measure child
657 child.measure(childWidthSpec, childHeightSpec);
658
659 int childLeft;
660 int childRight;
661
662 // Position vertically based on gravity setting
663 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800664 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
666 int childBottom = childTop + child.getMeasuredHeight();
667
668 int width = child.getMeasuredWidth();
669 childLeft = 0;
670 childRight = childLeft + width;
671
672 child.layout(childLeft, childTop, childRight, childBottom);
673 }
674
675 @Override
676 public boolean performClick() {
677 boolean handled = super.performClick();
678
679 if (!handled) {
680 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681
Adam Powellc3fa6302010-05-18 11:36:27 -0700682 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800683 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 }
686
687 return handled;
688 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800689
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 public void onClick(DialogInterface dialog, int which) {
691 setSelection(which);
692 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 }
694
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800695 @Override
696 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
697 super.onInitializeAccessibilityEvent(event);
698 event.setClassName(Spinner.class.getName());
699 }
700
701 @Override
702 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
703 super.onInitializeAccessibilityNodeInfo(info);
704 info.setClassName(Spinner.class.getName());
Alan Viverette058ac7c2013-08-19 16:44:30 -0700705
706 if (mAdapter != null) {
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700707 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700708 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800709 }
710
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 /**
712 * Sets the prompt to display when the dialog is shown.
713 * @param prompt the prompt to set
714 */
715 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700716 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717 }
718
719 /**
720 * Sets the prompt to display when the dialog is shown.
721 * @param promptId the resource ID of the prompt to display when the dialog is shown
722 */
723 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700724 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 }
726
727 /**
728 * @return The prompt to display when the dialog is shown
729 */
730 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700731 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 }
Adam Powell19fd1642011-02-07 19:00:11 -0800733
Adam Powellb70c7272011-02-10 12:04:59 -0800734 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800735 if (adapter == null) {
736 return 0;
737 }
738
739 int width = 0;
740 View itemView = null;
741 int itemType = 0;
742 final int widthMeasureSpec =
743 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
744 final int heightMeasureSpec =
745 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
746
747 // Make sure the number of items we'll measure is capped. If it's a huge data set
748 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800749 int start = Math.max(0, getSelectedItemPosition());
750 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
751 final int count = end - start;
752 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
753 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800754 final int positionType = adapter.getItemViewType(i);
755 if (positionType != itemType) {
756 itemType = positionType;
757 itemView = null;
758 }
759 itemView = adapter.getView(i, itemView, this);
760 if (itemView.getLayoutParams() == null) {
761 itemView.setLayoutParams(new ViewGroup.LayoutParams(
762 ViewGroup.LayoutParams.WRAP_CONTENT,
763 ViewGroup.LayoutParams.WRAP_CONTENT));
764 }
765 itemView.measure(widthMeasureSpec, heightMeasureSpec);
766 width = Math.max(width, itemView.getMeasuredWidth());
767 }
768
769 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800770 if (background != null) {
771 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800772 width += mTempRect.left + mTempRect.right;
773 }
774
775 return width;
776 }
777
Adam Powell235ae5f2012-12-10 13:38:03 -0800778 @Override
779 public Parcelable onSaveInstanceState() {
780 final SavedState ss = new SavedState(super.onSaveInstanceState());
781 ss.showDropdown = mPopup != null && mPopup.isShowing();
782 return ss;
783 }
784
785 @Override
786 public void onRestoreInstanceState(Parcelable state) {
787 SavedState ss = (SavedState) state;
788
789 super.onRestoreInstanceState(ss.getSuperState());
790
791 if (ss.showDropdown) {
792 ViewTreeObserver vto = getViewTreeObserver();
793 if (vto != null) {
794 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
795 @Override
796 public void onGlobalLayout() {
797 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800798 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800799 }
800 final ViewTreeObserver vto = getViewTreeObserver();
801 if (vto != null) {
802 vto.removeOnGlobalLayoutListener(this);
803 }
804 }
805 };
806 vto.addOnGlobalLayoutListener(listener);
807 }
808 }
809 }
810
811 static class SavedState extends AbsSpinner.SavedState {
812 boolean showDropdown;
813
814 SavedState(Parcelable superState) {
815 super(superState);
816 }
817
818 private SavedState(Parcel in) {
819 super(in);
820 showDropdown = in.readByte() != 0;
821 }
822
823 @Override
824 public void writeToParcel(Parcel out, int flags) {
825 super.writeToParcel(out, flags);
826 out.writeByte((byte) (showDropdown ? 1 : 0));
827 }
828
829 public static final Parcelable.Creator<SavedState> CREATOR =
830 new Parcelable.Creator<SavedState>() {
831 public SavedState createFromParcel(Parcel in) {
832 return new SavedState(in);
833 }
834
835 public SavedState[] newArray(int size) {
836 return new SavedState[size];
837 }
838 };
839 }
840
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800841 /**
842 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
843 * into a ListAdapter.</p>
844 */
845 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
846 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800847 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848
849 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800850 * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 *
852 * @param adapter the Adapter to transform into a ListAdapter
853 */
854 public DropDownAdapter(SpinnerAdapter adapter) {
855 this.mAdapter = adapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800856 if (adapter instanceof ListAdapter) {
857 this.mListAdapter = (ListAdapter) adapter;
858 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 }
860
861 public int getCount() {
862 return mAdapter == null ? 0 : mAdapter.getCount();
863 }
864
865 public Object getItem(int position) {
866 return mAdapter == null ? null : mAdapter.getItem(position);
867 }
868
869 public long getItemId(int position) {
870 return mAdapter == null ? -1 : mAdapter.getItemId(position);
871 }
872
873 public View getView(int position, View convertView, ViewGroup parent) {
874 return getDropDownView(position, convertView, parent);
875 }
876
877 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800878 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 }
880
881 public boolean hasStableIds() {
882 return mAdapter != null && mAdapter.hasStableIds();
883 }
884
885 public void registerDataSetObserver(DataSetObserver observer) {
886 if (mAdapter != null) {
887 mAdapter.registerDataSetObserver(observer);
888 }
889 }
890
891 public void unregisterDataSetObserver(DataSetObserver observer) {
892 if (mAdapter != null) {
893 mAdapter.unregisterDataSetObserver(observer);
894 }
895 }
896
897 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800898 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
899 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 */
901 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -0800902 final ListAdapter adapter = mListAdapter;
903 if (adapter != null) {
904 return adapter.areAllItemsEnabled();
905 } else {
906 return true;
907 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 }
909
910 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800911 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
912 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 */
914 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -0800915 final ListAdapter adapter = mListAdapter;
916 if (adapter != null) {
917 return adapter.isEnabled(position);
918 } else {
919 return true;
920 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 }
922
923 public int getItemViewType(int position) {
924 return 0;
925 }
926
927 public int getViewTypeCount() {
928 return 1;
929 }
930
931 public boolean isEmpty() {
932 return getCount() == 0;
933 }
934 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700935
936 /**
937 * Implements some sort of popup selection interface for selecting a spinner option.
938 * Allows for different spinner modes.
939 */
940 private interface SpinnerPopup {
941 public void setAdapter(ListAdapter adapter);
942
943 /**
944 * Show the popup
945 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800946 public void show(int textDirection, int textAlignment);
947
Adam Powellc3fa6302010-05-18 11:36:27 -0700948 /**
949 * Dismiss the popup
950 */
951 public void dismiss();
952
953 /**
954 * @return true if the popup is showing, false otherwise.
955 */
956 public boolean isShowing();
957
958 /**
959 * Set hint text to be displayed to the user. This should provide
960 * a description of the choice being made.
961 * @param hintText Hint text to set.
962 */
963 public void setPromptText(CharSequence hintText);
964 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -0800965
966 public void setBackgroundDrawable(Drawable bg);
967 public void setVerticalOffset(int px);
968 public void setHorizontalOffset(int px);
969 public Drawable getBackground();
970 public int getVerticalOffset();
971 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -0700972 }
973
974 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
975 private AlertDialog mPopup;
976 private ListAdapter mListAdapter;
977 private CharSequence mPrompt;
978
979 public void dismiss() {
Daniel 2 Olofsson2f77f9c2013-06-10 14:49:14 +0200980 if (mPopup != null) {
981 mPopup.dismiss();
982 mPopup = null;
983 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700984 }
985
986 public boolean isShowing() {
987 return mPopup != null ? mPopup.isShowing() : false;
988 }
989
990 public void setAdapter(ListAdapter adapter) {
991 mListAdapter = adapter;
992 }
993
994 public void setPromptText(CharSequence hintText) {
995 mPrompt = hintText;
996 }
997
998 public CharSequence getHintText() {
999 return mPrompt;
1000 }
1001
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001002 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -07001003 if (mListAdapter == null) {
1004 return;
1005 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001006 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
1007 if (mPrompt != null) {
1008 builder.setTitle(mPrompt);
1009 }
1010 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001011 getSelectedItemPosition(), this).create();
1012 final ListView listView = mPopup.getListView();
1013 listView.setTextDirection(textDirection);
1014 listView.setTextAlignment(textAlignment);
1015 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -07001016 }
1017
1018 public void onClick(DialogInterface dialog, int which) {
1019 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -08001020 if (mOnItemClickListener != null) {
1021 performItemClick(null, which, mListAdapter.getItemId(which));
1022 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001023 dismiss();
1024 }
Adam Powelld9c7be62012-03-08 19:43:43 -08001025
1026 @Override
1027 public void setBackgroundDrawable(Drawable bg) {
1028 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1029 }
1030
1031 @Override
1032 public void setVerticalOffset(int px) {
1033 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1034 }
1035
1036 @Override
1037 public void setHorizontalOffset(int px) {
1038 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1039 }
1040
1041 @Override
1042 public Drawable getBackground() {
1043 return null;
1044 }
1045
1046 @Override
1047 public int getVerticalOffset() {
1048 return 0;
1049 }
1050
1051 @Override
1052 public int getHorizontalOffset() {
1053 return 0;
1054 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001055 }
1056
1057 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1058 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001059 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001060
Alan Viverette617feb92013-09-09 18:09:13 -07001061 public DropdownPopup(
1062 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1063 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001064
Adam Powellc3fa6302010-05-18 11:36:27 -07001065 setAnchorView(Spinner.this);
1066 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001067 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001068 setOnItemClickListener(new OnItemClickListener() {
1069 public void onItemClick(AdapterView parent, View v, int position, long id) {
1070 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001071 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001072 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001073 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001074 dismiss();
1075 }
1076 });
1077 }
1078
Adam Powell19fd1642011-02-07 19:00:11 -08001079 @Override
1080 public void setAdapter(ListAdapter adapter) {
1081 super.setAdapter(adapter);
1082 mAdapter = adapter;
1083 }
1084
Adam Powellc3fa6302010-05-18 11:36:27 -07001085 public CharSequence getHintText() {
1086 return mHintText;
1087 }
1088
1089 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001090 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001091 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001092 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001093
Adam Powell235ae5f2012-12-10 13:38:03 -08001094 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001095 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001096 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001097 if (background != null) {
1098 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001099 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001100 } else {
1101 mTempRect.left = mTempRect.right = 0;
1102 }
1103
Adam Powell62e2bde2011-08-15 15:50:05 -07001104 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001105 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1106 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001107
Adam Powell8db7cb12011-02-08 14:18:38 -08001108 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001109 int contentWidth = measureContentWidth(
1110 (SpinnerAdapter) mAdapter, getBackground());
1111 final int contentWidthLimit = mContext.getResources()
1112 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1113 if (contentWidth > contentWidthLimit) {
1114 contentWidth = contentWidthLimit;
1115 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001116 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001117 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001118 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001119 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001120 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001121 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001122 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001123
1124 if (isLayoutRtl()) {
1125 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1126 } else {
1127 hOffset += spinnerPaddingLeft;
1128 }
1129 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001130 }
1131
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001132 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001133 final boolean wasShowing = isShowing();
1134
1135 computeContentWidth();
1136
Adam Powell6f5e9342011-01-27 13:30:55 -08001137 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001138 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001139 final ListView listView = getListView();
1140 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1141 listView.setTextDirection(textDirection);
1142 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001143 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001144
Adam Powell235ae5f2012-12-10 13:38:03 -08001145 if (wasShowing) {
1146 // Skip setting up the layout/dismiss listener below. If we were previously
1147 // showing it will still stick around.
1148 return;
1149 }
1150
Adam Powellf16daf62012-10-03 11:51:34 -07001151 // Make sure we hide if our anchor goes away.
1152 // TODO: This might be appropriate to push all the way down to PopupWindow,
1153 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1154 final ViewTreeObserver vto = getViewTreeObserver();
1155 if (vto != null) {
1156 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1157 @Override
1158 public void onGlobalLayout() {
1159 if (!Spinner.this.isVisibleToUser()) {
1160 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001161 } else {
1162 computeContentWidth();
1163
1164 // Use super.show here to update; we don't want to move the selected
1165 // position or adjust other things that would be reset otherwise.
1166 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001167 }
1168 }
1169 };
1170 vto.addOnGlobalLayoutListener(layoutListener);
1171 setOnDismissListener(new OnDismissListener() {
1172 @Override public void onDismiss() {
1173 final ViewTreeObserver vto = getViewTreeObserver();
1174 if (vto != null) {
1175 vto.removeOnGlobalLayoutListener(layoutListener);
1176 }
1177 }
1178 });
1179 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001180 }
1181 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182}