blob: 92acba56681447f603c5a5072a7cd07b04c059ef [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 Viveretteca6a3612013-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 Viveretteca6a3612013-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 Viveretteca6a3612013-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,
133 * and default style.
134 *
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.
138 * @param defStyle The default style to apply to this view. If 0, no style
139 * will be applied (beyond what is included in the theme). This may
140 * either be an attribute resource, whose value will be retrieved
141 * from the current theme, or an explicit style resource.
142 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 public Spinner(Context context, AttributeSet attrs, int defStyle) {
Adam Powellfef364f2010-09-02 15:11:46 -0700144 this(context, attrs, defStyle, MODE_THEME);
145 }
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.
155 * @param defStyle The default style to apply to this view. If 0, no style
156 * will be applied (beyond what is included in the theme). This may
157 * either be an attribute resource, whose value will be retrieved
158 * from the current theme, or an explicit style resource.
159 * @param mode Constant describing how the user will select choices from the spinner.
160 *
161 * @see #MODE_DIALOG
162 * @see #MODE_DROPDOWN
163 */
164 public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 super(context, attrs, defStyle);
166
167 TypedArray a = context.obtainStyledAttributes(attrs,
168 com.android.internal.R.styleable.Spinner, defStyle, 0);
Adam Powellfef364f2010-09-02 15:11:46 -0700169
170 if (mode == MODE_THEME) {
171 mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
172 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700173
174 switch (mode) {
175 case MODE_DIALOG: {
176 mPopup = new DialogPopup();
177 break;
178 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700179
Adam Powellc3fa6302010-05-18 11:36:27 -0700180 case MODE_DROPDOWN: {
Alan Viveretteca6a3612013-08-16 14:41:06 -0700181 final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
Adam Powellc3fa6302010-05-18 11:36:27 -0700182
Adam Powell8db7cb12011-02-08 14:18:38 -0800183 mDropDownWidth = a.getLayoutDimension(
Ben Komalo72536f72010-10-15 11:28:31 -0700184 com.android.internal.R.styleable.Spinner_dropDownWidth,
Adam Powell8db7cb12011-02-08 14:18:38 -0800185 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powellc3fa6302010-05-18 11:36:27 -0700186 popup.setBackgroundDrawable(a.getDrawable(
187 com.android.internal.R.styleable.Spinner_popupBackground));
Adam Powell8132ba52011-07-15 17:37:11 -0700188 final int verticalOffset = a.getDimensionPixelOffset(
189 com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0);
190 if (verticalOffset != 0) {
191 popup.setVerticalOffset(verticalOffset);
192 }
193
194 final int horizontalOffset = a.getDimensionPixelOffset(
195 com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0);
196 if (horizontalOffset != 0) {
197 popup.setHorizontalOffset(horizontalOffset);
198 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700199
200 mPopup = popup;
Alan Viveretteca6a3612013-08-16 14:41:06 -0700201 mForwardingListener = new ForwardingListener(context) {
202 @Override
203 public ListPopupWindow getPopup() {
204 return popup;
205 }
206
207 @Override
208 public boolean onForwardingStarted() {
209 if (!mPopup.isShowing()) {
210 mPopup.show(getTextDirection(), getTextAlignment());
211 }
212 return true;
213 }
214 };
Adam Powellc3fa6302010-05-18 11:36:27 -0700215 break;
216 }
217 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800218
Adam Powella39b9872011-01-05 16:07:54 -0800219 mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
220
Adam Powellc3fa6302010-05-18 11:36:27 -0700221 mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222
Adam Powell42b7e992011-11-08 09:47:32 -0800223 mDisableChildrenWhenDisabled = a.getBoolean(
224 com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700227
228 // Base constructor can call setAdapter before we initialize mPopup.
229 // Finish setting things up if this happened.
230 if (mTempAdapter != null) {
231 mPopup.setAdapter(mTempAdapter);
232 mTempAdapter = null;
233 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 }
Adam Powella39b9872011-01-05 16:07:54 -0800235
Adam Powelld9c7be62012-03-08 19:43:43 -0800236 /**
237 * Set the background drawable for the spinner's popup window of choices.
238 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
239 *
240 * @param background Background drawable
241 *
242 * @attr ref android.R.styleable#Spinner_popupBackground
243 */
244 public void setPopupBackgroundDrawable(Drawable background) {
245 if (!(mPopup instanceof DropdownPopup)) {
246 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
247 return;
248 }
249 ((DropdownPopup) mPopup).setBackgroundDrawable(background);
250 }
251
252 /**
253 * Set the background drawable for the spinner's popup window of choices.
254 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
255 *
Adam Powelldca510e2012-03-08 20:06:39 -0800256 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800257 *
258 * @attr ref android.R.styleable#Spinner_popupBackground
259 */
260 public void setPopupBackgroundResource(int resId) {
261 setPopupBackgroundDrawable(getContext().getResources().getDrawable(resId));
262 }
263
264 /**
265 * Get the background drawable for the spinner's popup window of choices.
266 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
267 *
268 * @return background Background drawable
269 *
270 * @attr ref android.R.styleable#Spinner_popupBackground
271 */
272 public Drawable getPopupBackground() {
273 return mPopup.getBackground();
274 }
275
276 /**
277 * Set a vertical offset in pixels for the spinner's popup window of choices.
278 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
279 *
280 * @param pixels Vertical offset in pixels
281 *
282 * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
283 */
284 public void setDropDownVerticalOffset(int pixels) {
285 mPopup.setVerticalOffset(pixels);
286 }
287
288 /**
289 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
290 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
291 *
292 * @return Vertical offset in pixels
293 *
294 * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
295 */
296 public int getDropDownVerticalOffset() {
297 return mPopup.getVerticalOffset();
298 }
299
300 /**
301 * Set a horizontal offset in pixels for the spinner's popup window of choices.
302 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
303 *
304 * @param pixels Horizontal offset in pixels
305 *
306 * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
307 */
308 public void setDropDownHorizontalOffset(int pixels) {
309 mPopup.setHorizontalOffset(pixels);
310 }
311
312 /**
313 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
314 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
315 *
316 * @return Horizontal offset in pixels
317 *
318 * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
319 */
320 public int getDropDownHorizontalOffset() {
321 return mPopup.getHorizontalOffset();
322 }
323
324 /**
325 * Set the width of the spinner's popup window of choices in pixels. This value
326 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
327 * to match the width of the Spinner itself, or
328 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
329 * of contained dropdown list items.
330 *
331 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
332 *
333 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
334 *
335 * @attr ref android.R.styleable#Spinner_dropDownWidth
336 */
337 public void setDropDownWidth(int pixels) {
338 if (!(mPopup instanceof DropdownPopup)) {
339 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
340 return;
341 }
342 mDropDownWidth = pixels;
343 }
344
345 /**
346 * Get the configured width of the spinner's popup window of choices in pixels.
347 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
348 * meaning the popup window will match the width of the Spinner itself, or
349 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
350 * of contained dropdown list items.
351 *
352 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
353 *
354 * @attr ref android.R.styleable#Spinner_dropDownWidth
355 */
356 public int getDropDownWidth() {
357 return mDropDownWidth;
358 }
359
Adam Powell42b7e992011-11-08 09:47:32 -0800360 @Override
361 public void setEnabled(boolean enabled) {
362 super.setEnabled(enabled);
363 if (mDisableChildrenWhenDisabled) {
364 final int count = getChildCount();
365 for (int i = 0; i < count; i++) {
366 getChildAt(i).setEnabled(enabled);
367 }
368 }
369 }
370
Adam Powella39b9872011-01-05 16:07:54 -0800371 /**
372 * Describes how the selected item view is positioned. Currently only the horizontal component
373 * is used. The default is determined by the current theme.
374 *
375 * @param gravity See {@link android.view.Gravity}
376 *
377 * @attr ref android.R.styleable#Spinner_gravity
378 */
379 public void setGravity(int gravity) {
380 if (mGravity != gravity) {
381 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700382 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800383 }
384 mGravity = gravity;
385 requestLayout();
386 }
387 }
388
Adam Powelld9c7be62012-03-08 19:43:43 -0800389 /**
390 * Describes how the selected item view is positioned. The default is determined by the
391 * current theme.
392 *
393 * @return A {@link android.view.Gravity Gravity} value
394 */
395 public int getGravity() {
396 return mGravity;
397 }
398
Alan Viverettea089dde2013-07-24 16:38:37 -0700399 /**
400 * Sets the Adapter used to provide the data which backs this Spinner.
401 * <p>
402 * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the
403 * Adapter associated with this view. Calling
404 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
405 * returned from {@link #getAdapter()} will always return 0. Calling
406 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
407 * 1.
408 *
409 * @see AbsSpinner#setAdapter(SpinnerAdapter)
410 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700411 @Override
412 public void setAdapter(SpinnerAdapter adapter) {
413 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700414
415 if (mPopup != null) {
416 mPopup.setAdapter(new DropDownAdapter(adapter));
417 } else {
418 mTempAdapter = new DropDownAdapter(adapter);
419 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700420 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421
422 @Override
423 public int getBaseline() {
424 View child = null;
425
426 if (getChildCount() > 0) {
427 child = getChildAt(0);
428 } else if (mAdapter != null && mAdapter.getCount() > 0) {
429 child = makeAndAddView(0);
Adam Powell22e92e52010-12-10 13:20:28 -0800430 mRecycler.put(0, child);
431 removeAllViewsInLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 }
433
434 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700435 final int childBaseline = child.getBaseline();
436 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 } else {
438 return -1;
439 }
440 }
441
Romain Guy5275d692009-07-15 17:00:23 -0700442 @Override
443 protected void onDetachedFromWindow() {
444 super.onDetachedFromWindow();
445
446 if (mPopup != null && mPopup.isShowing()) {
447 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700448 }
449 }
450
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 /**
452 * <p>A spinner does not support item click events. Calling this method
453 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700454 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 *
456 * @param l this listener will be ignored
457 */
458 @Override
459 public void setOnItemClickListener(OnItemClickListener l) {
460 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
461 }
462
Adam Powellc4e57e22012-02-24 19:24:26 -0800463 /**
464 * @hide internal use only
465 */
466 public void setOnItemClickListenerInt(OnItemClickListener l) {
467 super.setOnItemClickListener(l);
468 }
469
Adam Powella39b9872011-01-05 16:07:54 -0800470 @Override
Alan Viveretteca6a3612013-08-16 14:41:06 -0700471 public boolean onTouchEvent(MotionEvent event) {
472 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
473 return true;
474 }
475
476 return super.onTouchEvent(event);
477 }
478
479 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800480 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
481 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
482 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
483 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800484 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800485 measureContentWidth(getAdapter(), getBackground())),
486 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800487 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800488 }
489 }
490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 /**
492 * @see android.view.View#onLayout(boolean,int,int,int,int)
493 *
494 * Creates and positions all views
495 *
496 */
497 @Override
498 protected void onLayout(boolean changed, int l, int t, int r, int b) {
499 super.onLayout(changed, l, t, r, b);
500 mInLayout = true;
501 layout(0, false);
502 mInLayout = false;
503 }
504
505 /**
506 * Creates and positions all views for this Spinner.
507 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700508 * @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 -0800509 * so views are scrolling to the left. -1 means selection is moving to the left.
510 */
511 @Override
512 void layout(int delta, boolean animate) {
513 int childrenLeft = mSpinnerPadding.left;
514 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
515
516 if (mDataChanged) {
517 handleDataChanged();
518 }
519
520 // Handle the empty set by removing all views
521 if (mItemCount == 0) {
522 resetList();
523 return;
524 }
525
526 if (mNextSelectedPosition >= 0) {
527 setSelectedPositionInt(mNextSelectedPosition);
528 }
529
530 recycleAllViews();
531
532 // Clear out old views
533 removeAllViewsInLayout();
534
Adam Powella39b9872011-01-05 16:07:54 -0800535 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700537
538 if (mAdapter != null) {
539 View sel = makeAndAddView(mSelectedPosition);
540 int width = sel.getMeasuredWidth();
541 int selectedOffset = childrenLeft;
542 final int layoutDirection = getLayoutDirection();
543 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
544 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
545 case Gravity.CENTER_HORIZONTAL:
546 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
547 break;
548 case Gravity.RIGHT:
549 selectedOffset = childrenLeft + childrenWidth - width;
550 break;
551 }
552 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554
555 // Flush any cached views that did not get reused above
556 mRecycler.clear();
557
558 invalidate();
559
560 checkSelectionChanged();
561
562 mDataChanged = false;
563 mNeedSync = false;
564 setNextSelectedPositionInt(mSelectedPosition);
565 }
566
567 /**
568 * Obtain a view, either by pulling an existing view from the recycler or
569 * by getting a new one from the adapter. If we are animating, make sure
570 * there is enough information in the view's layout parameters to animate
571 * from the old to new positions.
572 *
573 * @param position Position in the spinner for the view to obtain
574 * @return A view that has been added to the spinner
575 */
576 private View makeAndAddView(int position) {
577
578 View child;
579
580 if (!mDataChanged) {
581 child = mRecycler.get(position);
582 if (child != null) {
583 // Position the view
584 setUpChild(child);
585
586 return child;
587 }
588 }
589
590 // Nothing found in the recycler -- ask the adapter for a view
591 child = mAdapter.getView(position, null, this);
592
593 // Position the view
594 setUpChild(child);
595
596 return child;
597 }
598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 /**
600 * Helper for makeAndAddView to set the position of a view
601 * and fill out its layout paramters.
602 *
603 * @param child The view to position
604 */
605 private void setUpChild(View child) {
606
607 // Respect layout params that are already in the view. Otherwise
608 // make some up...
609 ViewGroup.LayoutParams lp = child.getLayoutParams();
610 if (lp == null) {
611 lp = generateDefaultLayoutParams();
612 }
613
614 addViewInLayout(child, 0, lp);
615
616 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800617 if (mDisableChildrenWhenDisabled) {
618 child.setEnabled(isEnabled());
619 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620
621 // Get measure specs
622 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
623 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
624 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
625 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
626
627 // Measure child
628 child.measure(childWidthSpec, childHeightSpec);
629
630 int childLeft;
631 int childRight;
632
633 // Position vertically based on gravity setting
634 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800635 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
637 int childBottom = childTop + child.getMeasuredHeight();
638
639 int width = child.getMeasuredWidth();
640 childLeft = 0;
641 childRight = childLeft + width;
642
643 child.layout(childLeft, childTop, childRight, childBottom);
644 }
645
646 @Override
647 public boolean performClick() {
648 boolean handled = super.performClick();
649
650 if (!handled) {
651 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
Adam Powellc3fa6302010-05-18 11:36:27 -0700653 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800654 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800655 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800656 }
657
658 return handled;
659 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 public void onClick(DialogInterface dialog, int which) {
662 setSelection(which);
663 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 }
665
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800666 @Override
667 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
668 super.onInitializeAccessibilityEvent(event);
669 event.setClassName(Spinner.class.getName());
670 }
671
672 @Override
673 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
674 super.onInitializeAccessibilityNodeInfo(info);
675 info.setClassName(Spinner.class.getName());
676 }
677
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 /**
679 * Sets the prompt to display when the dialog is shown.
680 * @param prompt the prompt to set
681 */
682 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700683 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 }
685
686 /**
687 * Sets the prompt to display when the dialog is shown.
688 * @param promptId the resource ID of the prompt to display when the dialog is shown
689 */
690 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700691 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 }
693
694 /**
695 * @return The prompt to display when the dialog is shown
696 */
697 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700698 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800699 }
Adam Powell19fd1642011-02-07 19:00:11 -0800700
Adam Powellb70c7272011-02-10 12:04:59 -0800701 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800702 if (adapter == null) {
703 return 0;
704 }
705
706 int width = 0;
707 View itemView = null;
708 int itemType = 0;
709 final int widthMeasureSpec =
710 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
711 final int heightMeasureSpec =
712 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
713
714 // Make sure the number of items we'll measure is capped. If it's a huge data set
715 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800716 int start = Math.max(0, getSelectedItemPosition());
717 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
718 final int count = end - start;
719 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
720 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800721 final int positionType = adapter.getItemViewType(i);
722 if (positionType != itemType) {
723 itemType = positionType;
724 itemView = null;
725 }
726 itemView = adapter.getView(i, itemView, this);
727 if (itemView.getLayoutParams() == null) {
728 itemView.setLayoutParams(new ViewGroup.LayoutParams(
729 ViewGroup.LayoutParams.WRAP_CONTENT,
730 ViewGroup.LayoutParams.WRAP_CONTENT));
731 }
732 itemView.measure(widthMeasureSpec, heightMeasureSpec);
733 width = Math.max(width, itemView.getMeasuredWidth());
734 }
735
736 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800737 if (background != null) {
738 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800739 width += mTempRect.left + mTempRect.right;
740 }
741
742 return width;
743 }
744
Adam Powell235ae5f2012-12-10 13:38:03 -0800745 @Override
746 public Parcelable onSaveInstanceState() {
747 final SavedState ss = new SavedState(super.onSaveInstanceState());
748 ss.showDropdown = mPopup != null && mPopup.isShowing();
749 return ss;
750 }
751
752 @Override
753 public void onRestoreInstanceState(Parcelable state) {
754 SavedState ss = (SavedState) state;
755
756 super.onRestoreInstanceState(ss.getSuperState());
757
758 if (ss.showDropdown) {
759 ViewTreeObserver vto = getViewTreeObserver();
760 if (vto != null) {
761 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
762 @Override
763 public void onGlobalLayout() {
764 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800765 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800766 }
767 final ViewTreeObserver vto = getViewTreeObserver();
768 if (vto != null) {
769 vto.removeOnGlobalLayoutListener(this);
770 }
771 }
772 };
773 vto.addOnGlobalLayoutListener(listener);
774 }
775 }
776 }
777
778 static class SavedState extends AbsSpinner.SavedState {
779 boolean showDropdown;
780
781 SavedState(Parcelable superState) {
782 super(superState);
783 }
784
785 private SavedState(Parcel in) {
786 super(in);
787 showDropdown = in.readByte() != 0;
788 }
789
790 @Override
791 public void writeToParcel(Parcel out, int flags) {
792 super.writeToParcel(out, flags);
793 out.writeByte((byte) (showDropdown ? 1 : 0));
794 }
795
796 public static final Parcelable.Creator<SavedState> CREATOR =
797 new Parcelable.Creator<SavedState>() {
798 public SavedState createFromParcel(Parcel in) {
799 return new SavedState(in);
800 }
801
802 public SavedState[] newArray(int size) {
803 return new SavedState[size];
804 }
805 };
806 }
807
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 /**
809 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
810 * into a ListAdapter.</p>
811 */
812 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
813 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800814 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800815
816 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800817 * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 *
819 * @param adapter the Adapter to transform into a ListAdapter
820 */
821 public DropDownAdapter(SpinnerAdapter adapter) {
822 this.mAdapter = adapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800823 if (adapter instanceof ListAdapter) {
824 this.mListAdapter = (ListAdapter) adapter;
825 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 }
827
828 public int getCount() {
829 return mAdapter == null ? 0 : mAdapter.getCount();
830 }
831
832 public Object getItem(int position) {
833 return mAdapter == null ? null : mAdapter.getItem(position);
834 }
835
836 public long getItemId(int position) {
837 return mAdapter == null ? -1 : mAdapter.getItemId(position);
838 }
839
840 public View getView(int position, View convertView, ViewGroup parent) {
841 return getDropDownView(position, convertView, parent);
842 }
843
844 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800845 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 }
847
848 public boolean hasStableIds() {
849 return mAdapter != null && mAdapter.hasStableIds();
850 }
851
852 public void registerDataSetObserver(DataSetObserver observer) {
853 if (mAdapter != null) {
854 mAdapter.registerDataSetObserver(observer);
855 }
856 }
857
858 public void unregisterDataSetObserver(DataSetObserver observer) {
859 if (mAdapter != null) {
860 mAdapter.unregisterDataSetObserver(observer);
861 }
862 }
863
864 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800865 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
866 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 */
868 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -0800869 final ListAdapter adapter = mListAdapter;
870 if (adapter != null) {
871 return adapter.areAllItemsEnabled();
872 } else {
873 return true;
874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 }
876
877 /**
Adam Powell1f09c832010-02-18 18:13:22 -0800878 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
879 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800880 */
881 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -0800882 final ListAdapter adapter = mListAdapter;
883 if (adapter != null) {
884 return adapter.isEnabled(position);
885 } else {
886 return true;
887 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
889
890 public int getItemViewType(int position) {
891 return 0;
892 }
893
894 public int getViewTypeCount() {
895 return 1;
896 }
897
898 public boolean isEmpty() {
899 return getCount() == 0;
900 }
901 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700902
903 /**
904 * Implements some sort of popup selection interface for selecting a spinner option.
905 * Allows for different spinner modes.
906 */
907 private interface SpinnerPopup {
908 public void setAdapter(ListAdapter adapter);
909
910 /**
911 * Show the popup
912 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800913 public void show(int textDirection, int textAlignment);
914
Adam Powellc3fa6302010-05-18 11:36:27 -0700915 /**
916 * Dismiss the popup
917 */
918 public void dismiss();
919
920 /**
921 * @return true if the popup is showing, false otherwise.
922 */
923 public boolean isShowing();
924
925 /**
926 * Set hint text to be displayed to the user. This should provide
927 * a description of the choice being made.
928 * @param hintText Hint text to set.
929 */
930 public void setPromptText(CharSequence hintText);
931 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -0800932
933 public void setBackgroundDrawable(Drawable bg);
934 public void setVerticalOffset(int px);
935 public void setHorizontalOffset(int px);
936 public Drawable getBackground();
937 public int getVerticalOffset();
938 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -0700939 }
940
941 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
942 private AlertDialog mPopup;
943 private ListAdapter mListAdapter;
944 private CharSequence mPrompt;
945
946 public void dismiss() {
947 mPopup.dismiss();
948 mPopup = null;
949 }
950
951 public boolean isShowing() {
952 return mPopup != null ? mPopup.isShowing() : false;
953 }
954
955 public void setAdapter(ListAdapter adapter) {
956 mListAdapter = adapter;
957 }
958
959 public void setPromptText(CharSequence hintText) {
960 mPrompt = hintText;
961 }
962
963 public CharSequence getHintText() {
964 return mPrompt;
965 }
966
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800967 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -0700968 if (mListAdapter == null) {
969 return;
970 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700971 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
972 if (mPrompt != null) {
973 builder.setTitle(mPrompt);
974 }
975 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800976 getSelectedItemPosition(), this).create();
977 final ListView listView = mPopup.getListView();
978 listView.setTextDirection(textDirection);
979 listView.setTextAlignment(textAlignment);
980 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -0700981 }
982
983 public void onClick(DialogInterface dialog, int which) {
984 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -0800985 if (mOnItemClickListener != null) {
986 performItemClick(null, which, mListAdapter.getItemId(which));
987 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700988 dismiss();
989 }
Adam Powelld9c7be62012-03-08 19:43:43 -0800990
991 @Override
992 public void setBackgroundDrawable(Drawable bg) {
993 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
994 }
995
996 @Override
997 public void setVerticalOffset(int px) {
998 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
999 }
1000
1001 @Override
1002 public void setHorizontalOffset(int px) {
1003 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1004 }
1005
1006 @Override
1007 public Drawable getBackground() {
1008 return null;
1009 }
1010
1011 @Override
1012 public int getVerticalOffset() {
1013 return 0;
1014 }
1015
1016 @Override
1017 public int getHorizontalOffset() {
1018 return 0;
1019 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001020 }
1021
1022 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1023 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001024 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001025
Adam Powella39b9872011-01-05 16:07:54 -08001026 public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
Adam Powellc3fa6302010-05-18 11:36:27 -07001027 super(context, attrs, 0, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001028
Adam Powellc3fa6302010-05-18 11:36:27 -07001029 setAnchorView(Spinner.this);
1030 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001031 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001032 setOnItemClickListener(new OnItemClickListener() {
1033 public void onItemClick(AdapterView parent, View v, int position, long id) {
1034 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001035 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001036 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001037 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001038 dismiss();
1039 }
1040 });
1041 }
1042
Adam Powell19fd1642011-02-07 19:00:11 -08001043 @Override
1044 public void setAdapter(ListAdapter adapter) {
1045 super.setAdapter(adapter);
1046 mAdapter = adapter;
1047 }
1048
Adam Powellc3fa6302010-05-18 11:36:27 -07001049 public CharSequence getHintText() {
1050 return mHintText;
1051 }
1052
1053 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001054 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001055 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001056 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001057
Adam Powell235ae5f2012-12-10 13:38:03 -08001058 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001059 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001060 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001061 if (background != null) {
1062 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001063 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001064 } else {
1065 mTempRect.left = mTempRect.right = 0;
1066 }
1067
Adam Powell62e2bde2011-08-15 15:50:05 -07001068 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001069 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1070 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001071
Adam Powell8db7cb12011-02-08 14:18:38 -08001072 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001073 int contentWidth = measureContentWidth(
1074 (SpinnerAdapter) mAdapter, getBackground());
1075 final int contentWidthLimit = mContext.getResources()
1076 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1077 if (contentWidth > contentWidthLimit) {
1078 contentWidth = contentWidthLimit;
1079 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001080 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001081 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001082 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001083 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001084 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001085 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001086 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001087
1088 if (isLayoutRtl()) {
1089 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1090 } else {
1091 hOffset += spinnerPaddingLeft;
1092 }
1093 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001094 }
1095
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001096 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001097 final boolean wasShowing = isShowing();
1098
1099 computeContentWidth();
1100
Adam Powell6f5e9342011-01-27 13:30:55 -08001101 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001102 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001103 final ListView listView = getListView();
1104 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1105 listView.setTextDirection(textDirection);
1106 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001107 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001108
Adam Powell235ae5f2012-12-10 13:38:03 -08001109 if (wasShowing) {
1110 // Skip setting up the layout/dismiss listener below. If we were previously
1111 // showing it will still stick around.
1112 return;
1113 }
1114
Adam Powellf16daf62012-10-03 11:51:34 -07001115 // Make sure we hide if our anchor goes away.
1116 // TODO: This might be appropriate to push all the way down to PopupWindow,
1117 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1118 final ViewTreeObserver vto = getViewTreeObserver();
1119 if (vto != null) {
1120 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1121 @Override
1122 public void onGlobalLayout() {
1123 if (!Spinner.this.isVisibleToUser()) {
1124 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001125 } else {
1126 computeContentWidth();
1127
1128 // Use super.show here to update; we don't want to move the selected
1129 // position or adjust other things that would be reset otherwise.
1130 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001131 }
1132 }
1133 };
1134 vto.addOnGlobalLayoutListener(layoutListener);
1135 setOnDismissListener(new OnDismissListener() {
1136 @Override public void onDismiss() {
1137 final ViewTreeObserver vto = getViewTreeObserver();
1138 if (vto != null) {
1139 vto.removeOnGlobalLayoutListener(layoutListener);
1140 }
1141 }
1142 });
1143 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001144 }
1145 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146}