blob: 92fcea348b1a3d0f01238b41463b946a3d66e6bb [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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.DrawableRes;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080020import android.annotation.Nullable;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070021import android.annotation.TestApi;
Mathew Inwood978c6e22018-08-21 15:58:55 +010022import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.annotation.Widget;
24import android.app.AlertDialog;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.DialogInterface.OnClickListener;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080028import android.content.res.Resources;
Alan Viverette2add9bc2015-06-02 14:54:40 -070029import android.content.res.Resources.Theme;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.content.res.TypedArray;
31import android.database.DataSetObserver;
Adam Powell5f83a602011-01-19 17:58:04 -080032import android.graphics.Rect;
Adam Powell8db7cb12011-02-08 14:18:38 -080033import android.graphics.drawable.Drawable;
Alan Viveretted5269772014-06-17 16:43:45 -070034import android.os.Build;
Adam Powell235ae5f2012-12-10 13:38:03 -080035import android.os.Parcel;
36import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.util.AttributeSet;
Adam Powelld9c7be62012-03-08 19:43:43 -080038import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080039import android.view.ContextThemeWrapper;
Adam Powella39b9872011-01-05 16:07:54 -080040import android.view.Gravity;
Alan Viveretteca6a36112013-08-16 14:41:06 -070041import android.view.MotionEvent;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070042import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.View;
44import android.view.ViewGroup;
Adam Powellf16daf62012-10-03 11:51:34 -070045import android.view.ViewTreeObserver;
46import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080047import android.view.accessibility.AccessibilityNodeInfo;
Ashley Rose55f9f922019-01-28 19:29:36 -050048import android.view.inspector.InspectableProperty;
Adam Powellf16daf62012-10-03 11:51:34 -070049import android.widget.PopupWindow.OnDismissListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
Aurimas Liutikas99441c52016-10-11 16:48:32 -070051import com.android.internal.R;
52import com.android.internal.view.menu.ShowableListMenu;
53
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054/**
55 * A view that displays one child at a time and lets the user pick among them.
56 * The items in the Spinner come from the {@link Adapter} associated with
57 * this view.
Scott Main41ec6532010-08-19 16:57:07 -070058 *
Scott Main4c359b72012-07-24 15:51:27 -070059 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
SeongJae Park95148492012-02-29 01:56:43 +090060 *
Scott Main4c359b72012-07-24 15:51:27 -070061 * @attr ref android.R.styleable#Spinner_dropDownSelector
Scott Main4c359b72012-07-24 15:51:27 -070062 * @attr ref android.R.styleable#Spinner_dropDownWidth
63 * @attr ref android.R.styleable#Spinner_gravity
64 * @attr ref android.R.styleable#Spinner_popupBackground
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 * @attr ref android.R.styleable#Spinner_prompt
Scott Main4c359b72012-07-24 15:51:27 -070066 * @attr ref android.R.styleable#Spinner_spinnerMode
Alan Viverette40ce0702014-08-28 15:27:04 -070067 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
68 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 */
70@Widget
71public class Spinner extends AbsSpinner implements OnClickListener {
Adam Powellc3fa6302010-05-18 11:36:27 -070072 private static final String TAG = "Spinner";
SeongJae Park95148492012-02-29 01:56:43 +090073
Adam Powell50f784c2010-12-19 16:12:19 -080074 // Only measure this many items to get a decent max width.
75 private static final int MAX_ITEMS_MEASURED = 15;
76
Adam Powellc3fa6302010-05-18 11:36:27 -070077 /**
78 * Use a dialog window for selecting spinner options.
79 */
80 public static final int MODE_DIALOG = 0;
SeongJae Park95148492012-02-29 01:56:43 +090081
Adam Powellc3fa6302010-05-18 11:36:27 -070082 /**
83 * Use a dropdown anchored to the Spinner for selecting spinner options.
84 */
85 public static final int MODE_DROPDOWN = 1;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080086
Adam Powellfef364f2010-09-02 15:11:46 -070087 /**
88 * Use the theme-supplied value to select the dropdown mode.
89 */
90 private static final int MODE_THEME = -1;
Alan Viveretteca6a36112013-08-16 14:41:06 -070091
Alan Viverettecd703b62015-08-12 11:14:33 -040092 private final Rect mTempRect = new Rect();
93
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080094 /** Context used to inflate the popup window or dialog. */
Alan Viverettecd703b62015-08-12 11:14:33 -040095 private final Context mPopupContext;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080096
Alan Viveretteca6a36112013-08-16 14:41:06 -070097 /** Forwarding listener used to implement drag-to-open. */
Mathew Inwood978c6e22018-08-21 15:58:55 +010098 @UnsupportedAppUsage
Alan Viveretteca6a36112013-08-16 14:41:06 -070099 private ForwardingListener mForwardingListener;
100
Alan Viverette3f221cf2015-01-16 14:40:04 -0800101 /** Temporary holder for setAdapter() calls from the super constructor. */
102 private SpinnerAdapter mTempAdapter;
103
Mathew Inwood978c6e22018-08-21 15:58:55 +0100104 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -0700105 private SpinnerPopup mPopup;
Adam Powell8db7cb12011-02-08 14:18:38 -0800106 int mDropDownWidth;
Adam Powellfef364f2010-09-02 15:11:46 -0700107
Adam Powella39b9872011-01-05 16:07:54 -0800108 private int mGravity;
Adam Powell42b7e992011-11-08 09:47:32 -0800109 private boolean mDisableChildrenWhenDisabled;
Adam Powella39b9872011-01-05 16:07:54 -0800110
Adam Powellfef364f2010-09-02 15:11:46 -0700111 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400112 * Constructs a new spinner with the given context's theme.
Adam Powellfef364f2010-09-02 15:11:46 -0700113 *
114 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400115 * access the current theme, resources, etc.
Adam Powellfef364f2010-09-02 15:11:46 -0700116 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 public Spinner(Context context) {
118 this(context, null);
119 }
120
Adam Powellfef364f2010-09-02 15:11:46 -0700121 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400122 * Constructs a new spinner with the given context's theme and the supplied
Adam Powellfef364f2010-09-02 15:11:46 -0700123 * mode of displaying choices. <code>mode</code> may be one of
124 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
125 *
126 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400127 * access the current theme, resources, etc.
128 * @param mode Constant describing how the user will select choices from
129 * the spinner.
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700130 *
Adam Powellfef364f2010-09-02 15:11:46 -0700131 * @see #MODE_DIALOG
132 * @see #MODE_DROPDOWN
133 */
134 public Spinner(Context context, int mode) {
135 this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
136 }
137
138 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400139 * Constructs a new spinner with the given context's theme and the supplied
140 * attribute set.
Adam Powellfef364f2010-09-02 15:11:46 -0700141 *
142 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400143 * access the current theme, resources, etc.
Adam Powellfef364f2010-09-02 15:11:46 -0700144 * @param attrs The attributes of the XML tag that is inflating the view.
145 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 public Spinner(Context context, AttributeSet attrs) {
147 this(context, attrs, com.android.internal.R.attr.spinnerStyle);
148 }
149
Adam Powellfef364f2010-09-02 15:11:46 -0700150 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400151 * Constructs a new spinner with the given context's theme, the supplied
152 * attribute set, and default style attribute.
Adam Powellfef364f2010-09-02 15:11:46 -0700153 *
154 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400155 * access the current theme, resources, etc.
Adam Powellfef364f2010-09-02 15:11:46 -0700156 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700157 * @param defStyleAttr An attribute in the current theme that contains a
Alan Viverettecd703b62015-08-12 11:14:33 -0400158 * reference to a style resource that supplies default
159 * values for the view. Can be 0 to not look for
160 * defaults.
Adam Powellfef364f2010-09-02 15:11:46 -0700161 */
Alan Viverette617feb92013-09-09 18:09:13 -0700162 public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
163 this(context, attrs, defStyleAttr, 0, MODE_THEME);
Adam Powellfef364f2010-09-02 15:11:46 -0700164 }
165
166 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400167 * Constructs a new spinner with the given context's theme, the supplied
168 * attribute set, and default style attribute. <code>mode</code> may be one
169 * of {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN} and determines how the
170 * user will select choices from the spinner.
Adam Powellfef364f2010-09-02 15:11:46 -0700171 *
172 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400173 * access the current theme, resources, etc.
Adam Powellfef364f2010-09-02 15:11:46 -0700174 * @param attrs The attributes of the XML tag that is inflating the view.
Alan Viverette617feb92013-09-09 18:09:13 -0700175 * @param defStyleAttr An attribute in the current theme that contains a
Alan Viverettecd703b62015-08-12 11:14:33 -0400176 * reference to a style resource that supplies default
177 * values for the view. Can be 0 to not look for defaults.
178 * @param mode Constant describing how the user will select choices from the
179 * spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700180 *
Adam Powellfef364f2010-09-02 15:11:46 -0700181 * @see #MODE_DIALOG
182 * @see #MODE_DROPDOWN
183 */
Alan Viverette617feb92013-09-09 18:09:13 -0700184 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
185 this(context, attrs, defStyleAttr, 0, mode);
186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187
Alan Viverette617feb92013-09-09 18:09:13 -0700188 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400189 * Constructs a new spinner with the given context's theme, the supplied
190 * attribute set, and default styles. <code>mode</code> may be one of
191 * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN} and determines how the
192 * user will select choices from the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700193 *
194 * @param context The Context the view is running in, through which it can
Alan Viverettecd703b62015-08-12 11:14:33 -0400195 * access the current theme, resources, etc.
Alan Viverette617feb92013-09-09 18:09:13 -0700196 * @param attrs The attributes of the XML tag that is inflating the view.
197 * @param defStyleAttr An attribute in the current theme that contains a
Alan Viverettecd703b62015-08-12 11:14:33 -0400198 * reference to a style resource that supplies default
199 * values for the view. Can be 0 to not look for
200 * defaults.
Alan Viverette617feb92013-09-09 18:09:13 -0700201 * @param defStyleRes A resource identifier of a style resource that
Alan Viverettecd703b62015-08-12 11:14:33 -0400202 * supplies default values for the view, used only if
203 * defStyleAttr is 0 or can not be found in the theme.
204 * Can be 0 to not look for defaults.
205 * @param mode Constant describing how the user will select choices from
206 * the spinner.
Alan Viverette617feb92013-09-09 18:09:13 -0700207 *
208 * @see #MODE_DIALOG
209 * @see #MODE_DROPDOWN
210 */
Alan Viverette3f221cf2015-01-16 14:40:04 -0800211 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
212 int mode) {
213 this(context, attrs, defStyleAttr, defStyleRes, mode, null);
214 }
215
216 /**
Alan Viverettecd703b62015-08-12 11:14:33 -0400217 * Constructs a new spinner with the given context, the supplied attribute
218 * set, default styles, popup mode (one of {@link #MODE_DIALOG} or
219 * {@link #MODE_DROPDOWN}), and the theme against which the popup should be
220 * inflated.
Alan Viverette3f221cf2015-01-16 14:40:04 -0800221 *
222 * @param context The context against which the view is inflated, which
223 * provides access to the current theme, resources, etc.
224 * @param attrs The attributes of the XML tag that is inflating the view.
225 * @param defStyleAttr An attribute in the current theme that contains a
226 * reference to a style resource that supplies default
227 * values for the view. Can be 0 to not look for
228 * defaults.
229 * @param defStyleRes A resource identifier of a style resource that
230 * supplies default values for the view, used only if
231 * defStyleAttr is 0 or can not be found in the theme.
232 * Can be 0 to not look for defaults.
233 * @param mode Constant describing how the user will select choices from
234 * the spinner.
Alan Viverette2add9bc2015-06-02 14:54:40 -0700235 * @param popupTheme The theme against which the dialog or dropdown popup
236 * should be inflated. May be {@code null} to use the
237 * view theme. If set, this will override any value
238 * specified by
239 * {@link android.R.styleable#Spinner_popupTheme}.
Alan Viverette3f221cf2015-01-16 14:40:04 -0800240 *
241 * @see #MODE_DIALOG
242 * @see #MODE_DROPDOWN
243 */
244 public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
Alan Viverette2add9bc2015-06-02 14:54:40 -0700245 Theme popupTheme) {
Alan Viverette617feb92013-09-09 18:09:13 -0700246 super(context, attrs, defStyleAttr, defStyleRes);
247
248 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800249 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800250 saveAttributeDataForStyleable(context, R.styleable.Spinner,
251 attrs, a, defStyleAttr, defStyleRes);
Adam Powellfef364f2010-09-02 15:11:46 -0700252
Alan Viverette2add9bc2015-06-02 14:54:40 -0700253 if (popupTheme != null) {
254 mPopupContext = new ContextThemeWrapper(context, popupTheme);
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800255 } else {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800256 final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0);
257 if (popupThemeResId != 0) {
258 mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
259 } else {
260 mPopupContext = context;
261 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800262 }
263
Adam Powellfef364f2010-09-02 15:11:46 -0700264 if (mode == MODE_THEME) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800265 mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
Adam Powellfef364f2010-09-02 15:11:46 -0700266 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800267
Adam Powellc3fa6302010-05-18 11:36:27 -0700268 switch (mode) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800269 case MODE_DIALOG: {
270 mPopup = new DialogPopup();
271 mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
272 break;
273 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -0700274
Alan Viverette3f221cf2015-01-16 14:40:04 -0800275 case MODE_DROPDOWN: {
276 final DropdownPopup popup = new DropdownPopup(
277 mPopupContext, attrs, defStyleAttr, defStyleRes);
278 final TypedArray pa = mPopupContext.obtainStyledAttributes(
279 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
280 mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth,
281 ViewGroup.LayoutParams.WRAP_CONTENT);
Alan Viverette850cd722016-01-11 15:34:43 -0500282 if (pa.hasValueOrEmpty(R.styleable.Spinner_dropDownSelector)) {
283 popup.setListSelector(pa.getDrawable(
284 R.styleable.Spinner_dropDownSelector));
285 }
Alan Viverette3f221cf2015-01-16 14:40:04 -0800286 popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
287 popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
288 pa.recycle();
Adam Powellc3fa6302010-05-18 11:36:27 -0700289
Alan Viverette3f221cf2015-01-16 14:40:04 -0800290 mPopup = popup;
291 mForwardingListener = new ForwardingListener(this) {
292 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700293 public ShowableListMenu getPopup() {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800294 return popup;
Alan Viveretteca6a36112013-08-16 14:41:06 -0700295 }
Alan Viverette3f221cf2015-01-16 14:40:04 -0800296
297 @Override
298 public boolean onForwardingStarted() {
299 if (!mPopup.isShowing()) {
300 mPopup.show(getTextDirection(), getTextAlignment());
301 }
302 return true;
303 }
304 };
305 break;
306 }
Adam Powellc3fa6302010-05-18 11:36:27 -0700307 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800308
Alan Viverette3f221cf2015-01-16 14:40:04 -0800309 mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
Adam Powell42b7e992011-11-08 09:47:32 -0800310 mDisableChildrenWhenDisabled = a.getBoolean(
Alan Viverette3f221cf2015-01-16 14:40:04 -0800311 R.styleable.Spinner_disableChildrenWhenDisabled, false);
Adam Powell42b7e992011-11-08 09:47:32 -0800312
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 a.recycle();
Adam Powell68464a92010-06-07 12:48:07 -0700314
315 // Base constructor can call setAdapter before we initialize mPopup.
316 // Finish setting things up if this happened.
317 if (mTempAdapter != null) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800318 setAdapter(mTempAdapter);
Adam Powell68464a92010-06-07 12:48:07 -0700319 mTempAdapter = null;
320 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 }
Adam Powella39b9872011-01-05 16:07:54 -0800322
Adam Powelld9c7be62012-03-08 19:43:43 -0800323 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800324 * @return the context used to inflate the Spinner's popup or dialog window
325 */
326 public Context getPopupContext() {
327 return mPopupContext;
328 }
329
330 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800331 * Set the background drawable for the spinner's popup window of choices.
332 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
333 *
334 * @param background Background drawable
335 *
336 * @attr ref android.R.styleable#Spinner_popupBackground
337 */
338 public void setPopupBackgroundDrawable(Drawable background) {
339 if (!(mPopup instanceof DropdownPopup)) {
340 Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
341 return;
342 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800343 mPopup.setBackgroundDrawable(background);
Adam Powelld9c7be62012-03-08 19:43:43 -0800344 }
345
346 /**
347 * Set the background drawable for the spinner's popup window of choices.
348 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
349 *
Adam Powelldca510e2012-03-08 20:06:39 -0800350 * @param resId Resource ID of a background drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800351 *
352 * @attr ref android.R.styleable#Spinner_popupBackground
353 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700354 public void setPopupBackgroundResource(@DrawableRes int resId) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800355 setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800356 }
357
358 /**
359 * Get the background drawable for the spinner's popup window of choices.
360 * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
361 *
362 * @return background Background drawable
363 *
364 * @attr ref android.R.styleable#Spinner_popupBackground
365 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500366 @InspectableProperty
Adam Powelld9c7be62012-03-08 19:43:43 -0800367 public Drawable getPopupBackground() {
368 return mPopup.getBackground();
369 }
370
371 /**
Kirill Grouchnikovfd62b352016-04-08 17:52:26 -0400372 * @hide
373 */
374 @TestApi
375 public boolean isPopupShowing() {
376 return (mPopup != null) && mPopup.isShowing();
377 }
378
379 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800380 * Set a vertical offset in pixels for the spinner's popup window of choices.
381 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
382 *
383 * @param pixels Vertical offset in pixels
384 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700385 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800386 */
387 public void setDropDownVerticalOffset(int pixels) {
388 mPopup.setVerticalOffset(pixels);
389 }
390
391 /**
392 * Get the configured vertical offset in pixels for the spinner's popup window of choices.
393 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
394 *
395 * @return Vertical offset in pixels
396 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700397 * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800398 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500399 @InspectableProperty
Adam Powelld9c7be62012-03-08 19:43:43 -0800400 public int getDropDownVerticalOffset() {
401 return mPopup.getVerticalOffset();
402 }
403
404 /**
405 * Set a horizontal offset in pixels for the spinner's popup window of choices.
406 * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
407 *
408 * @param pixels Horizontal offset in pixels
409 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700410 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800411 */
412 public void setDropDownHorizontalOffset(int pixels) {
413 mPopup.setHorizontalOffset(pixels);
414 }
415
416 /**
417 * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
418 * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
419 *
420 * @return Horizontal offset in pixels
421 *
Alan Viverette40ce0702014-08-28 15:27:04 -0700422 * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
Adam Powelld9c7be62012-03-08 19:43:43 -0800423 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500424 @InspectableProperty
Adam Powelld9c7be62012-03-08 19:43:43 -0800425 public int getDropDownHorizontalOffset() {
426 return mPopup.getHorizontalOffset();
427 }
428
429 /**
430 * Set the width of the spinner's popup window of choices in pixels. This value
431 * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
432 * to match the width of the Spinner itself, or
433 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
434 * of contained dropdown list items.
435 *
436 * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
437 *
438 * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
439 *
440 * @attr ref android.R.styleable#Spinner_dropDownWidth
441 */
442 public void setDropDownWidth(int pixels) {
443 if (!(mPopup instanceof DropdownPopup)) {
444 Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
445 return;
446 }
447 mDropDownWidth = pixels;
448 }
449
450 /**
451 * Get the configured width of the spinner's popup window of choices in pixels.
452 * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
453 * meaning the popup window will match the width of the Spinner itself, or
454 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
455 * of contained dropdown list items.
456 *
457 * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
458 *
459 * @attr ref android.R.styleable#Spinner_dropDownWidth
460 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500461 @InspectableProperty
Adam Powelld9c7be62012-03-08 19:43:43 -0800462 public int getDropDownWidth() {
463 return mDropDownWidth;
464 }
465
Adam Powell42b7e992011-11-08 09:47:32 -0800466 @Override
467 public void setEnabled(boolean enabled) {
468 super.setEnabled(enabled);
469 if (mDisableChildrenWhenDisabled) {
470 final int count = getChildCount();
471 for (int i = 0; i < count; i++) {
472 getChildAt(i).setEnabled(enabled);
473 }
474 }
475 }
476
Adam Powella39b9872011-01-05 16:07:54 -0800477 /**
478 * Describes how the selected item view is positioned. Currently only the horizontal component
479 * is used. The default is determined by the current theme.
480 *
481 * @param gravity See {@link android.view.Gravity}
482 *
483 * @attr ref android.R.styleable#Spinner_gravity
484 */
485 public void setGravity(int gravity) {
486 if (mGravity != gravity) {
487 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700488 gravity |= Gravity.START;
Adam Powella39b9872011-01-05 16:07:54 -0800489 }
490 mGravity = gravity;
491 requestLayout();
492 }
493 }
494
Adam Powelld9c7be62012-03-08 19:43:43 -0800495 /**
496 * Describes how the selected item view is positioned. The default is determined by the
497 * current theme.
498 *
499 * @return A {@link android.view.Gravity Gravity} value
500 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500501 @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
Adam Powelld9c7be62012-03-08 19:43:43 -0800502 public int getGravity() {
503 return mGravity;
504 }
505
Alan Viverettea089dde2013-07-24 16:38:37 -0700506 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800507 * Sets the {@link SpinnerAdapter} used to provide the data which backs
508 * this Spinner.
Alan Viverettea089dde2013-07-24 16:38:37 -0700509 * <p>
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800510 * If this Spinner has a popup theme set in XML via the
511 * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the
512 * adapter should inflate drop-down views using the same theme. The easiest
513 * way to achieve this is by using {@link #getPopupContext()} to obtain a
514 * layout inflater for use in
515 * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}.
516 * <p>
517 * Spinner overrides {@link Adapter#getViewTypeCount()} on the
Alan Viverettea089dde2013-07-24 16:38:37 -0700518 * Adapter associated with this view. Calling
519 * {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
520 * returned from {@link #getAdapter()} will always return 0. Calling
521 * {@link Adapter#getViewTypeCount() getViewTypeCount()} will always return
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700522 * 1. On API {@link Build.VERSION_CODES#LOLLIPOP} and above, attempting to set an
Alan Viveretted5269772014-06-17 16:43:45 -0700523 * adapter with more than one view type will throw an
524 * {@link IllegalArgumentException}.
525 *
526 * @param adapter the adapter to set
Alan Viverettea089dde2013-07-24 16:38:37 -0700527 *
528 * @see AbsSpinner#setAdapter(SpinnerAdapter)
Alan Viveretted5269772014-06-17 16:43:45 -0700529 * @throws IllegalArgumentException if the adapter has more than one view
530 * type
Alan Viverettea089dde2013-07-24 16:38:37 -0700531 */
Adam Powellc3fa6302010-05-18 11:36:27 -0700532 @Override
533 public void setAdapter(SpinnerAdapter adapter) {
Alan Viverette3f221cf2015-01-16 14:40:04 -0800534 // The super constructor may call setAdapter before we're prepared.
535 // Postpone doing anything until we've finished construction.
536 if (mPopup == null) {
537 mTempAdapter = adapter;
538 return;
539 }
540
Adam Powellc3fa6302010-05-18 11:36:27 -0700541 super.setAdapter(adapter);
Adam Powell68464a92010-06-07 12:48:07 -0700542
Adam Powell6a221b32013-09-10 11:38:52 -0700543 mRecycler.clear();
544
Alan Viveretted5269772014-06-17 16:43:45 -0700545 final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
Dianne Hackborn955d8d62014-10-07 20:17:19 -0700546 if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
Alan Viveretted5269772014-06-17 16:43:45 -0700547 && adapter != null && adapter.getViewTypeCount() != 1) {
548 throw new IllegalArgumentException("Spinner adapter view type count must be 1");
549 }
550
Alan Viverette3f221cf2015-01-16 14:40:04 -0800551 final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
552 mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
Adam Powellc3fa6302010-05-18 11:36:27 -0700553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554
555 @Override
556 public int getBaseline() {
557 View child = null;
558
559 if (getChildCount() > 0) {
560 child = getChildAt(0);
561 } else if (mAdapter != null && mAdapter.getCount() > 0) {
Adam Powell6a221b32013-09-10 11:38:52 -0700562 child = makeView(0, false);
Adam Powell22e92e52010-12-10 13:20:28 -0800563 mRecycler.put(0, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 }
565
566 if (child != null) {
Adam Powell160bb7f2011-07-07 10:22:27 -0700567 final int childBaseline = child.getBaseline();
568 return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 } else {
570 return -1;
571 }
572 }
573
Romain Guy5275d692009-07-15 17:00:23 -0700574 @Override
575 protected void onDetachedFromWindow() {
576 super.onDetachedFromWindow();
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700577
Romain Guy5275d692009-07-15 17:00:23 -0700578 if (mPopup != null && mPopup.isShowing()) {
579 mPopup.dismiss();
Romain Guy5275d692009-07-15 17:00:23 -0700580 }
581 }
582
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 /**
584 * <p>A spinner does not support item click events. Calling this method
585 * will raise an exception.</p>
Scott Main4c359b72012-07-24 15:51:27 -0700586 * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800587 *
588 * @param l this listener will be ignored
589 */
590 @Override
591 public void setOnItemClickListener(OnItemClickListener l) {
592 throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
593 }
594
Adam Powellc4e57e22012-02-24 19:24:26 -0800595 /**
596 * @hide internal use only
597 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100598 @UnsupportedAppUsage
Adam Powellc4e57e22012-02-24 19:24:26 -0800599 public void setOnItemClickListenerInt(OnItemClickListener l) {
600 super.setOnItemClickListener(l);
601 }
602
Adam Powella39b9872011-01-05 16:07:54 -0800603 @Override
Alan Viveretteca6a36112013-08-16 14:41:06 -0700604 public boolean onTouchEvent(MotionEvent event) {
605 if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
606 return true;
607 }
608
609 return super.onTouchEvent(event);
610 }
611
612 @Override
Adam Powella39b9872011-01-05 16:07:54 -0800613 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
614 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
615 if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
616 final int measuredWidth = getMeasuredWidth();
Adam Powell19fd1642011-02-07 19:00:11 -0800617 setMeasuredDimension(Math.min(Math.max(measuredWidth,
Adam Powellb70c7272011-02-10 12:04:59 -0800618 measureContentWidth(getAdapter(), getBackground())),
619 MeasureSpec.getSize(widthMeasureSpec)),
Adam Powella39b9872011-01-05 16:07:54 -0800620 getMeasuredHeight());
Adam Powella39b9872011-01-05 16:07:54 -0800621 }
622 }
623
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 /**
625 * @see android.view.View#onLayout(boolean,int,int,int,int)
626 *
627 * Creates and positions all views
628 *
629 */
630 @Override
631 protected void onLayout(boolean changed, int l, int t, int r, int b) {
632 super.onLayout(changed, l, t, r, b);
633 mInLayout = true;
634 layout(0, false);
635 mInLayout = false;
636 }
637
638 /**
639 * Creates and positions all views for this Spinner.
640 *
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -0700641 * @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 -0800642 * so views are scrolling to the left. -1 means selection is moving to the left.
643 */
644 @Override
645 void layout(int delta, boolean animate) {
646 int childrenLeft = mSpinnerPadding.left;
647 int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
648
649 if (mDataChanged) {
650 handleDataChanged();
651 }
652
653 // Handle the empty set by removing all views
654 if (mItemCount == 0) {
655 resetList();
656 return;
657 }
658
659 if (mNextSelectedPosition >= 0) {
660 setSelectedPositionInt(mNextSelectedPosition);
661 }
662
663 recycleAllViews();
664
665 // Clear out old views
666 removeAllViewsInLayout();
667
Adam Powella39b9872011-01-05 16:07:54 -0800668 // Make selected view and position it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 mFirstPosition = mSelectedPosition;
Adam Powell72d574c2013-04-10 11:27:08 -0700670
671 if (mAdapter != null) {
Adam Powell6a221b32013-09-10 11:38:52 -0700672 View sel = makeView(mSelectedPosition, true);
Adam Powell72d574c2013-04-10 11:27:08 -0700673 int width = sel.getMeasuredWidth();
674 int selectedOffset = childrenLeft;
675 final int layoutDirection = getLayoutDirection();
676 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
677 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
678 case Gravity.CENTER_HORIZONTAL:
679 selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
680 break;
681 case Gravity.RIGHT:
682 selectedOffset = childrenLeft + childrenWidth - width;
683 break;
684 }
685 sel.offsetLeftAndRight(selectedOffset);
Adam Powella39b9872011-01-05 16:07:54 -0800686 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687
688 // Flush any cached views that did not get reused above
689 mRecycler.clear();
690
691 invalidate();
692
693 checkSelectionChanged();
694
695 mDataChanged = false;
696 mNeedSync = false;
697 setNextSelectedPositionInt(mSelectedPosition);
698 }
699
700 /**
701 * Obtain a view, either by pulling an existing view from the recycler or
702 * by getting a new one from the adapter. If we are animating, make sure
703 * there is enough information in the view's layout parameters to animate
704 * from the old to new positions.
705 *
706 * @param position Position in the spinner for the view to obtain
Adam Powell6a221b32013-09-10 11:38:52 -0700707 * @param addChild true to add the child to the spinner, false to obtain and configure only.
708 * @return A view for the given position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 */
Adam Powell6a221b32013-09-10 11:38:52 -0700710 private View makeView(int position, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 View child;
712
713 if (!mDataChanged) {
714 child = mRecycler.get(position);
715 if (child != null) {
716 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700717 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718
719 return child;
720 }
721 }
722
723 // Nothing found in the recycler -- ask the adapter for a view
724 child = mAdapter.getView(position, null, this);
725
726 // Position the view
Adam Powell6a221b32013-09-10 11:38:52 -0700727 setUpChild(child, addChild);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728
729 return child;
730 }
731
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 /**
733 * Helper for makeAndAddView to set the position of a view
734 * and fill out its layout paramters.
735 *
736 * @param child The view to position
Adam Powell6a221b32013-09-10 11:38:52 -0700737 * @param addChild true if the child should be added to the Spinner during setup
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 */
Adam Powell6a221b32013-09-10 11:38:52 -0700739 private void setUpChild(View child, boolean addChild) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740
741 // Respect layout params that are already in the view. Otherwise
742 // make some up...
743 ViewGroup.LayoutParams lp = child.getLayoutParams();
744 if (lp == null) {
745 lp = generateDefaultLayoutParams();
746 }
747
Alan Viverette25c6cd62015-06-22 15:25:55 -0700748 addViewInLayout(child, 0, lp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749
750 child.setSelected(hasFocus());
Adam Powell42b7e992011-11-08 09:47:32 -0800751 if (mDisableChildrenWhenDisabled) {
752 child.setEnabled(isEnabled());
753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754
755 // Get measure specs
756 int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
757 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
758 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
759 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
760
761 // Measure child
762 child.measure(childWidthSpec, childHeightSpec);
763
764 int childLeft;
765 int childRight;
766
767 // Position vertically based on gravity setting
768 int childTop = mSpinnerPadding.top
Dianne Hackborn189ee182010-12-02 21:48:53 -0800769 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
771 int childBottom = childTop + child.getMeasuredHeight();
772
773 int width = child.getMeasuredWidth();
774 childLeft = 0;
775 childRight = childLeft + width;
776
777 child.layout(childLeft, childTop, childRight, childBottom);
Alan Viverette25c6cd62015-06-22 15:25:55 -0700778
779 if (!addChild) {
780 removeViewInLayout(child);
781 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 }
783
784 @Override
785 public boolean performClick() {
786 boolean handled = super.performClick();
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 if (!handled) {
789 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790
Adam Powellc3fa6302010-05-18 11:36:27 -0700791 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800792 mPopup.show(getTextDirection(), getTextAlignment());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 }
795
796 return handled;
797 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800798
Felipe Lemed09ccb82017-02-22 15:02:03 -0800799 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 public void onClick(DialogInterface dialog, int which) {
801 setSelection(which);
802 dialog.dismiss();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800803 }
804
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800805 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800806 public CharSequence getAccessibilityClassName() {
807 return Spinner.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800808 }
809
Alan Viverettea54956a2015-01-07 16:05:02 -0800810 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800811 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800812 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
813 super.onInitializeAccessibilityNodeInfoInternal(info);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700814
815 if (mAdapter != null) {
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700816 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700817 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800818 }
819
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 /**
821 * Sets the prompt to display when the dialog is shown.
822 * @param prompt the prompt to set
823 */
824 public void setPrompt(CharSequence prompt) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700825 mPopup.setPromptText(prompt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 }
827
828 /**
829 * Sets the prompt to display when the dialog is shown.
830 * @param promptId the resource ID of the prompt to display when the dialog is shown
831 */
832 public void setPromptId(int promptId) {
Adam Powellc3fa6302010-05-18 11:36:27 -0700833 setPrompt(getContext().getText(promptId));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 }
835
836 /**
837 * @return The prompt to display when the dialog is shown
838 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500839 @InspectableProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800840 public CharSequence getPrompt() {
Adam Powellc3fa6302010-05-18 11:36:27 -0700841 return mPopup.getHintText();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 }
Adam Powell19fd1642011-02-07 19:00:11 -0800843
Adam Powellb70c7272011-02-10 12:04:59 -0800844 int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
Adam Powell19fd1642011-02-07 19:00:11 -0800845 if (adapter == null) {
846 return 0;
847 }
848
849 int width = 0;
850 View itemView = null;
851 int itemType = 0;
852 final int widthMeasureSpec =
Adam Powelld5dbf4b2015-06-11 13:19:24 -0700853 MeasureSpec.makeSafeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED);
Adam Powell19fd1642011-02-07 19:00:11 -0800854 final int heightMeasureSpec =
Adam Powelld5dbf4b2015-06-11 13:19:24 -0700855 MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED);
Adam Powell19fd1642011-02-07 19:00:11 -0800856
857 // Make sure the number of items we'll measure is capped. If it's a huge data set
858 // with wildly varying sizes, oh well.
Adam Powellb70c7272011-02-10 12:04:59 -0800859 int start = Math.max(0, getSelectedItemPosition());
860 final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
861 final int count = end - start;
862 start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
863 for (int i = start; i < end; i++) {
Adam Powell19fd1642011-02-07 19:00:11 -0800864 final int positionType = adapter.getItemViewType(i);
865 if (positionType != itemType) {
866 itemType = positionType;
867 itemView = null;
868 }
869 itemView = adapter.getView(i, itemView, this);
870 if (itemView.getLayoutParams() == null) {
871 itemView.setLayoutParams(new ViewGroup.LayoutParams(
872 ViewGroup.LayoutParams.WRAP_CONTENT,
873 ViewGroup.LayoutParams.WRAP_CONTENT));
874 }
875 itemView.measure(widthMeasureSpec, heightMeasureSpec);
876 width = Math.max(width, itemView.getMeasuredWidth());
877 }
878
879 // Add background padding to measured width
Adam Powellb70c7272011-02-10 12:04:59 -0800880 if (background != null) {
881 background.getPadding(mTempRect);
Adam Powell19fd1642011-02-07 19:00:11 -0800882 width += mTempRect.left + mTempRect.right;
883 }
884
885 return width;
886 }
887
Adam Powell235ae5f2012-12-10 13:38:03 -0800888 @Override
889 public Parcelable onSaveInstanceState() {
890 final SavedState ss = new SavedState(super.onSaveInstanceState());
891 ss.showDropdown = mPopup != null && mPopup.isShowing();
892 return ss;
893 }
894
895 @Override
896 public void onRestoreInstanceState(Parcelable state) {
897 SavedState ss = (SavedState) state;
898
899 super.onRestoreInstanceState(ss.getSuperState());
900
901 if (ss.showDropdown) {
902 ViewTreeObserver vto = getViewTreeObserver();
903 if (vto != null) {
904 final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
905 @Override
906 public void onGlobalLayout() {
907 if (!mPopup.isShowing()) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -0800908 mPopup.show(getTextDirection(), getTextAlignment());
Adam Powell235ae5f2012-12-10 13:38:03 -0800909 }
910 final ViewTreeObserver vto = getViewTreeObserver();
911 if (vto != null) {
912 vto.removeOnGlobalLayoutListener(this);
913 }
914 }
915 };
916 vto.addOnGlobalLayoutListener(listener);
917 }
918 }
919 }
920
Vladislav Kaznacheev2a848ff2016-09-23 10:16:16 -0700921 @Override
922 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
923 if (getPointerIcon() == null && isClickable() && isEnabled()) {
924 return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
925 }
926 return super.onResolvePointerIcon(event, pointerIndex);
927 }
928
Adam Powell235ae5f2012-12-10 13:38:03 -0800929 static class SavedState extends AbsSpinner.SavedState {
930 boolean showDropdown;
931
932 SavedState(Parcelable superState) {
933 super(superState);
934 }
935
936 private SavedState(Parcel in) {
937 super(in);
938 showDropdown = in.readByte() != 0;
939 }
940
941 @Override
942 public void writeToParcel(Parcel out, int flags) {
943 super.writeToParcel(out, flags);
944 out.writeByte((byte) (showDropdown ? 1 : 0));
945 }
946
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700947 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
Adam Powell235ae5f2012-12-10 13:38:03 -0800948 new Parcelable.Creator<SavedState>() {
949 public SavedState createFromParcel(Parcel in) {
950 return new SavedState(in);
951 }
952
953 public SavedState[] newArray(int size) {
954 return new SavedState[size];
955 }
956 };
957 }
958
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800959 /**
960 * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
961 * into a ListAdapter.</p>
962 */
963 private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
964 private SpinnerAdapter mAdapter;
Adam Powell1f09c832010-02-18 18:13:22 -0800965 private ListAdapter mListAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966
967 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800968 * Creates a new ListAdapter wrapper for the specified adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 *
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800970 * @param adapter the SpinnerAdapter to transform into a ListAdapter
971 * @param dropDownTheme the theme against which to inflate drop-down
972 * views, may be {@null} to use default theme
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 */
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800974 public DropDownAdapter(@Nullable SpinnerAdapter adapter,
975 @Nullable Resources.Theme dropDownTheme) {
976 mAdapter = adapter;
977
Adam Powell1f09c832010-02-18 18:13:22 -0800978 if (adapter instanceof ListAdapter) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800979 mListAdapter = (ListAdapter) adapter;
980 }
981
Alan Viverette2add9bc2015-06-02 14:54:40 -0700982 if (dropDownTheme != null && adapter instanceof ThemedSpinnerAdapter) {
983 final ThemedSpinnerAdapter themedAdapter = (ThemedSpinnerAdapter) adapter;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800984 if (themedAdapter.getDropDownViewTheme() == null) {
985 themedAdapter.setDropDownViewTheme(dropDownTheme);
986 }
Adam Powell1f09c832010-02-18 18:13:22 -0800987 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 }
989
990 public int getCount() {
991 return mAdapter == null ? 0 : mAdapter.getCount();
992 }
993
994 public Object getItem(int position) {
995 return mAdapter == null ? null : mAdapter.getItem(position);
996 }
997
998 public long getItemId(int position) {
999 return mAdapter == null ? -1 : mAdapter.getItemId(position);
1000 }
1001
1002 public View getView(int position, View convertView, ViewGroup parent) {
1003 return getDropDownView(position, convertView, parent);
1004 }
1005
1006 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001007 return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 }
1009
1010 public boolean hasStableIds() {
1011 return mAdapter != null && mAdapter.hasStableIds();
1012 }
1013
1014 public void registerDataSetObserver(DataSetObserver observer) {
1015 if (mAdapter != null) {
1016 mAdapter.registerDataSetObserver(observer);
1017 }
1018 }
1019
1020 public void unregisterDataSetObserver(DataSetObserver observer) {
1021 if (mAdapter != null) {
1022 mAdapter.unregisterDataSetObserver(observer);
1023 }
1024 }
1025
1026 /**
Adam Powell1f09c832010-02-18 18:13:22 -08001027 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001028 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029 */
1030 public boolean areAllItemsEnabled() {
Adam Powell1f09c832010-02-18 18:13:22 -08001031 final ListAdapter adapter = mListAdapter;
1032 if (adapter != null) {
1033 return adapter.areAllItemsEnabled();
1034 } else {
1035 return true;
1036 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 }
1038
1039 /**
Adam Powell1f09c832010-02-18 18:13:22 -08001040 * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
1041 * Otherwise, return true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 */
1043 public boolean isEnabled(int position) {
Adam Powell1f09c832010-02-18 18:13:22 -08001044 final ListAdapter adapter = mListAdapter;
1045 if (adapter != null) {
1046 return adapter.isEnabled(position);
1047 } else {
1048 return true;
1049 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 }
1051
1052 public int getItemViewType(int position) {
1053 return 0;
1054 }
1055
1056 public int getViewTypeCount() {
1057 return 1;
1058 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001059
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 public boolean isEmpty() {
1061 return getCount() == 0;
1062 }
1063 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001064
Adam Powellc3fa6302010-05-18 11:36:27 -07001065 /**
1066 * Implements some sort of popup selection interface for selecting a spinner option.
1067 * Allows for different spinner modes.
1068 */
1069 private interface SpinnerPopup {
1070 public void setAdapter(ListAdapter adapter);
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001071
Adam Powellc3fa6302010-05-18 11:36:27 -07001072 /**
1073 * Show the popup
1074 */
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001075 public void show(int textDirection, int textAlignment);
1076
Adam Powellc3fa6302010-05-18 11:36:27 -07001077 /**
1078 * Dismiss the popup
1079 */
1080 public void dismiss();
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001081
Adam Powellc3fa6302010-05-18 11:36:27 -07001082 /**
1083 * @return true if the popup is showing, false otherwise.
1084 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001085 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -07001086 public boolean isShowing();
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001087
Adam Powellc3fa6302010-05-18 11:36:27 -07001088 /**
1089 * Set hint text to be displayed to the user. This should provide
1090 * a description of the choice being made.
1091 * @param hintText Hint text to set.
1092 */
1093 public void setPromptText(CharSequence hintText);
1094 public CharSequence getHintText();
Adam Powelld9c7be62012-03-08 19:43:43 -08001095
1096 public void setBackgroundDrawable(Drawable bg);
1097 public void setVerticalOffset(int px);
1098 public void setHorizontalOffset(int px);
1099 public Drawable getBackground();
1100 public int getVerticalOffset();
1101 public int getHorizontalOffset();
Adam Powellc3fa6302010-05-18 11:36:27 -07001102 }
Alan Viverette3f221cf2015-01-16 14:40:04 -08001103
Adam Powellc3fa6302010-05-18 11:36:27 -07001104 private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
1105 private AlertDialog mPopup;
1106 private ListAdapter mListAdapter;
1107 private CharSequence mPrompt;
1108
1109 public void dismiss() {
Daniel 2 Olofsson2f77f9c2013-06-10 14:49:14 +02001110 if (mPopup != null) {
1111 mPopup.dismiss();
1112 mPopup = null;
1113 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001114 }
1115
Mathew Inwood978c6e22018-08-21 15:58:55 +01001116 @UnsupportedAppUsage
Adam Powellc3fa6302010-05-18 11:36:27 -07001117 public boolean isShowing() {
1118 return mPopup != null ? mPopup.isShowing() : false;
1119 }
1120
1121 public void setAdapter(ListAdapter adapter) {
1122 mListAdapter = adapter;
1123 }
1124
1125 public void setPromptText(CharSequence hintText) {
1126 mPrompt = hintText;
1127 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001128
Adam Powellc3fa6302010-05-18 11:36:27 -07001129 public CharSequence getHintText() {
1130 return mPrompt;
1131 }
1132
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001133 public void show(int textDirection, int textAlignment) {
Alan Viveretteb9867ea2013-07-29 19:07:55 -07001134 if (mListAdapter == null) {
1135 return;
1136 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001137 AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
Adam Powellc3fa6302010-05-18 11:36:27 -07001138 if (mPrompt != null) {
1139 builder.setTitle(mPrompt);
1140 }
1141 mPopup = builder.setSingleChoiceItems(mListAdapter,
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001142 getSelectedItemPosition(), this).create();
1143 final ListView listView = mPopup.getListView();
1144 listView.setTextDirection(textDirection);
1145 listView.setTextAlignment(textAlignment);
1146 mPopup.show();
Adam Powellc3fa6302010-05-18 11:36:27 -07001147 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001148
Adam Powellc3fa6302010-05-18 11:36:27 -07001149 public void onClick(DialogInterface dialog, int which) {
1150 setSelection(which);
Adam Powellc4e57e22012-02-24 19:24:26 -08001151 if (mOnItemClickListener != null) {
1152 performItemClick(null, which, mListAdapter.getItemId(which));
1153 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001154 dismiss();
1155 }
Adam Powelld9c7be62012-03-08 19:43:43 -08001156
1157 @Override
1158 public void setBackgroundDrawable(Drawable bg) {
1159 Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1160 }
1161
1162 @Override
1163 public void setVerticalOffset(int px) {
1164 Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1165 }
1166
1167 @Override
1168 public void setHorizontalOffset(int px) {
1169 Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1170 }
1171
1172 @Override
1173 public Drawable getBackground() {
1174 return null;
1175 }
1176
1177 @Override
1178 public int getVerticalOffset() {
1179 return 0;
1180 }
1181
1182 @Override
1183 public int getHorizontalOffset() {
1184 return 0;
1185 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001186 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001187
Adam Powellc3fa6302010-05-18 11:36:27 -07001188 private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1189 private CharSequence mHintText;
Adam Powell19fd1642011-02-07 19:00:11 -08001190 private ListAdapter mAdapter;
Chris Yergaefd08112011-01-17 00:30:08 -08001191
Alan Viverette617feb92013-09-09 18:09:13 -07001192 public DropdownPopup(
1193 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1194 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell50f784c2010-12-19 16:12:19 -08001195
Adam Powellc3fa6302010-05-18 11:36:27 -07001196 setAnchorView(Spinner.this);
1197 setModal(true);
Adam Powellbe4d68e2010-10-08 18:16:34 -07001198 setPromptPosition(POSITION_PROMPT_ABOVE);
Adam Powellc3fa6302010-05-18 11:36:27 -07001199 setOnItemClickListener(new OnItemClickListener() {
1200 public void onItemClick(AdapterView parent, View v, int position, long id) {
1201 Spinner.this.setSelection(position);
Adam Powellc4e57e22012-02-24 19:24:26 -08001202 if (mOnItemClickListener != null) {
Adam Powellaf363132012-04-12 18:14:12 -07001203 Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
Adam Powellc4e57e22012-02-24 19:24:26 -08001204 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001205 dismiss();
1206 }
1207 });
1208 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001209
Adam Powell19fd1642011-02-07 19:00:11 -08001210 @Override
1211 public void setAdapter(ListAdapter adapter) {
1212 super.setAdapter(adapter);
1213 mAdapter = adapter;
1214 }
1215
Adam Powellc3fa6302010-05-18 11:36:27 -07001216 public CharSequence getHintText() {
1217 return mHintText;
1218 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001219
Adam Powellc3fa6302010-05-18 11:36:27 -07001220 public void setPromptText(CharSequence hintText) {
Adam Powella39b9872011-01-05 16:07:54 -08001221 // Hint text is ignored for dropdowns, but maintain it here.
Adam Powellc3fa6302010-05-18 11:36:27 -07001222 mHintText = hintText;
Adam Powellc3fa6302010-05-18 11:36:27 -07001223 }
Daisuke Miyakawa3f10b1c2010-08-28 15:59:56 -07001224
Adam Powell235ae5f2012-12-10 13:38:03 -08001225 void computeContentWidth() {
SeongJae Park95148492012-02-29 01:56:43 +09001226 final Drawable background = getBackground();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001227 int hOffset = 0;
SeongJae Park95148492012-02-29 01:56:43 +09001228 if (background != null) {
1229 background.getPadding(mTempRect);
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001230 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
SeongJae Park95148492012-02-29 01:56:43 +09001231 } else {
1232 mTempRect.left = mTempRect.right = 0;
1233 }
1234
Adam Powell62e2bde2011-08-15 15:50:05 -07001235 final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001236 final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1237 final int spinnerWidth = Spinner.this.getWidth();
Adam Powell235ae5f2012-12-10 13:38:03 -08001238
Adam Powell8db7cb12011-02-08 14:18:38 -08001239 if (mDropDownWidth == WRAP_CONTENT) {
SeongJae Park95148492012-02-29 01:56:43 +09001240 int contentWidth = measureContentWidth(
1241 (SpinnerAdapter) mAdapter, getBackground());
1242 final int contentWidthLimit = mContext.getResources()
1243 .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
1244 if (contentWidth > contentWidthLimit) {
1245 contentWidth = contentWidthLimit;
1246 }
Adam Powell62e2bde2011-08-15 15:50:05 -07001247 setContentWidth(Math.max(
SeongJae Park95148492012-02-29 01:56:43 +09001248 contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
Adam Powell8db7cb12011-02-08 14:18:38 -08001249 } else if (mDropDownWidth == MATCH_PARENT) {
Adam Powell62e2bde2011-08-15 15:50:05 -07001250 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
Adam Powell8db7cb12011-02-08 14:18:38 -08001251 } else {
Adam Powell62e2bde2011-08-15 15:50:05 -07001252 setContentWidth(mDropDownWidth);
Adam Powell8db7cb12011-02-08 14:18:38 -08001253 }
Fabrice Di Meglio38d64c52012-06-06 15:57:39 -07001254
1255 if (isLayoutRtl()) {
1256 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1257 } else {
1258 hOffset += spinnerPaddingLeft;
1259 }
1260 setHorizontalOffset(hOffset);
Adam Powell235ae5f2012-12-10 13:38:03 -08001261 }
1262
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001263 public void show(int textDirection, int textAlignment) {
Adam Powell235ae5f2012-12-10 13:38:03 -08001264 final boolean wasShowing = isShowing();
1265
1266 computeContentWidth();
1267
Adam Powell6f5e9342011-01-27 13:30:55 -08001268 setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
Adam Powellc3fa6302010-05-18 11:36:27 -07001269 super.show();
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08001270 final ListView listView = getListView();
1271 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1272 listView.setTextDirection(textDirection);
1273 listView.setTextAlignment(textAlignment);
Adam Powellc3fa6302010-05-18 11:36:27 -07001274 setSelection(Spinner.this.getSelectedItemPosition());
Adam Powellf16daf62012-10-03 11:51:34 -07001275
Adam Powell235ae5f2012-12-10 13:38:03 -08001276 if (wasShowing) {
1277 // Skip setting up the layout/dismiss listener below. If we were previously
1278 // showing it will still stick around.
1279 return;
1280 }
1281
Adam Powellf16daf62012-10-03 11:51:34 -07001282 // Make sure we hide if our anchor goes away.
1283 // TODO: This might be appropriate to push all the way down to PopupWindow,
1284 // but it may have other side effects to investigate first. (Text editing handles, etc.)
1285 final ViewTreeObserver vto = getViewTreeObserver();
1286 if (vto != null) {
1287 final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
1288 @Override
1289 public void onGlobalLayout() {
1290 if (!Spinner.this.isVisibleToUser()) {
1291 dismiss();
Adam Powell235ae5f2012-12-10 13:38:03 -08001292 } else {
1293 computeContentWidth();
1294
1295 // Use super.show here to update; we don't want to move the selected
1296 // position or adjust other things that would be reset otherwise.
1297 DropdownPopup.super.show();
Adam Powellf16daf62012-10-03 11:51:34 -07001298 }
1299 }
1300 };
1301 vto.addOnGlobalLayoutListener(layoutListener);
1302 setOnDismissListener(new OnDismissListener() {
1303 @Override public void onDismiss() {
1304 final ViewTreeObserver vto = getViewTreeObserver();
1305 if (vto != null) {
1306 vto.removeOnGlobalLayoutListener(layoutListener);
1307 }
1308 }
1309 });
1310 }
Adam Powellc3fa6302010-05-18 11:36:27 -07001311 }
1312 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -08001313
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314}