1 /*
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 
17 package android.widget;
18 
19 import android.annotation.DrawableRes;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.annotation.Widget;
23 import android.app.AlertDialog;
24 import android.compat.annotation.UnsupportedAppUsage;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.DialogInterface.OnClickListener;
28 import android.content.res.Resources;
29 import android.content.res.Resources.Theme;
30 import android.content.res.TypedArray;
31 import android.database.DataSetObserver;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.os.Build;
35 import android.os.Parcel;
36 import android.os.Parcelable;
37 import android.util.AttributeSet;
38 import android.util.Log;
39 import android.view.ContextThemeWrapper;
40 import android.view.Gravity;
41 import android.view.MotionEvent;
42 import android.view.PointerIcon;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.view.ViewTreeObserver;
46 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
47 import android.view.accessibility.AccessibilityNodeInfo;
48 import android.view.inspector.InspectableProperty;
49 import android.widget.PopupWindow.OnDismissListener;
50 
51 import com.android.internal.R;
52 import com.android.internal.view.menu.ShowableListMenu;
53 
54 /**
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.
58  *
59  * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p>
60  *
61  * @attr ref android.R.styleable#Spinner_dropDownSelector
62  * @attr ref android.R.styleable#Spinner_dropDownWidth
63  * @attr ref android.R.styleable#Spinner_gravity
64  * @attr ref android.R.styleable#Spinner_popupBackground
65  * @attr ref android.R.styleable#Spinner_prompt
66  * @attr ref android.R.styleable#Spinner_spinnerMode
67  * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
68  * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
69  */
70 @Widget
71 public class Spinner extends AbsSpinner implements OnClickListener {
72     private static final String TAG = "Spinner";
73 
74     // Only measure this many items to get a decent max width.
75     private static final int MAX_ITEMS_MEASURED = 15;
76 
77     /**
78      * Use a dialog window for selecting spinner options.
79      */
80     public static final int MODE_DIALOG = 0;
81 
82     /**
83      * Use a dropdown anchored to the Spinner for selecting spinner options.
84      */
85     public static final int MODE_DROPDOWN = 1;
86 
87     /**
88      * Use the theme-supplied value to select the dropdown mode.
89      */
90     private static final int MODE_THEME = -1;
91 
92     private final Rect mTempRect = new Rect();
93 
94     /** Context used to inflate the popup window or dialog. */
95     private final Context mPopupContext;
96 
97     /** Forwarding listener used to implement drag-to-open. */
98     @UnsupportedAppUsage
99     private ForwardingListener mForwardingListener;
100 
101     /** Temporary holder for setAdapter() calls from the super constructor. */
102     private SpinnerAdapter mTempAdapter;
103 
104     @UnsupportedAppUsage
105     private SpinnerPopup mPopup;
106     int mDropDownWidth;
107 
108     private int mGravity;
109     private boolean mDisableChildrenWhenDisabled;
110 
111     /**
112      * Constructs a new spinner with the given context's theme.
113      *
114      * @param context The Context the view is running in, through which it can
115      *                access the current theme, resources, etc.
116      */
Spinner(Context context)117     public Spinner(Context context) {
118         this(context, null);
119     }
120 
121     /**
122      * Constructs a new spinner with the given context's theme and the supplied
123      * 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
127      *                access the current theme, resources, etc.
128      * @param mode Constant describing how the user will select choices from
129      *             the spinner.
130      *
131      * @see #MODE_DIALOG
132      * @see #MODE_DROPDOWN
133      */
Spinner(Context context, int mode)134     public Spinner(Context context, int mode) {
135         this(context, null, com.android.internal.R.attr.spinnerStyle, mode);
136     }
137 
138     /**
139      * Constructs a new spinner with the given context's theme and the supplied
140      * attribute set.
141      *
142      * @param context The Context the view is running in, through which it can
143      *                access the current theme, resources, etc.
144      * @param attrs The attributes of the XML tag that is inflating the view.
145      */
Spinner(Context context, AttributeSet attrs)146     public Spinner(Context context, AttributeSet attrs) {
147         this(context, attrs, com.android.internal.R.attr.spinnerStyle);
148     }
149 
150     /**
151      * Constructs a new spinner with the given context's theme, the supplied
152      * attribute set, and default style attribute.
153      *
154      * @param context The Context the view is running in, through which it can
155      *                access the current theme, resources, etc.
156      * @param attrs The attributes of the XML tag that is inflating the view.
157      * @param defStyleAttr An attribute in the current theme that contains a
158      *                     reference to a style resource that supplies default
159      *                     values for the view. Can be 0 to not look for
160      *                     defaults.
161      */
Spinner(Context context, AttributeSet attrs, int defStyleAttr)162     public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
163         this(context, attrs, defStyleAttr, 0, MODE_THEME);
164     }
165 
166     /**
167      * 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.
171      *
172      * @param context The Context the view is running in, through which it can
173      *                access the current theme, resources, etc.
174      * @param attrs The attributes of the XML tag that is inflating the view.
175      * @param defStyleAttr An attribute in the current theme that contains a
176      *                     reference to a style resource that supplies default
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.
180      *
181      * @see #MODE_DIALOG
182      * @see #MODE_DROPDOWN
183      */
Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode)184     public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
185         this(context, attrs, defStyleAttr, 0, mode);
186     }
187 
188     /**
189      * 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.
193      *
194      * @param context The Context the view is running in, through which it can
195      *                access the current theme, resources, etc.
196      * @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
198      *                     reference to a style resource that supplies default
199      *                     values for the view. Can be 0 to not look for
200      *                     defaults.
201      * @param defStyleRes A resource identifier of a style resource that
202      *                    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.
207      *
208      * @see #MODE_DIALOG
209      * @see #MODE_DROPDOWN
210      */
Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode)211     public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
212             int mode) {
213         this(context, attrs, defStyleAttr, defStyleRes, mode, null);
214     }
215 
216     /**
217      * 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.
221      *
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.
235      * @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}.
240      *
241      * @see #MODE_DIALOG
242      * @see #MODE_DROPDOWN
243      */
Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode, Theme popupTheme)244     public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
245             Theme popupTheme) {
246         super(context, attrs, defStyleAttr, defStyleRes);
247 
248         final TypedArray a = context.obtainStyledAttributes(
249                 attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
250         saveAttributeDataForStyleable(context, R.styleable.Spinner,
251                 attrs, a, defStyleAttr, defStyleRes);
252 
253         if (popupTheme != null) {
254             mPopupContext = new ContextThemeWrapper(context, popupTheme);
255         } else {
256             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             }
262         }
263 
264         if (mode == MODE_THEME) {
265             mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
266         }
267 
268         switch (mode) {
269             case MODE_DIALOG: {
270                 mPopup = new DialogPopup();
271                 mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
272                 break;
273             }
274 
275             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);
282                 if (pa.hasValueOrEmpty(R.styleable.Spinner_dropDownSelector)) {
283                     popup.setListSelector(pa.getDrawable(
284                             R.styleable.Spinner_dropDownSelector));
285                 }
286                 popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
287                 popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
288                 pa.recycle();
289 
290                 mPopup = popup;
291                 mForwardingListener = new ForwardingListener(this) {
292                     @Override
293                     public ShowableListMenu getPopup() {
294                         return popup;
295                     }
296 
297                     @Override
298                     public boolean onForwardingStarted() {
299                         if (!mPopup.isShowing()) {
300                             mPopup.show(getTextDirection(), getTextAlignment());
301                         }
302                         return true;
303                     }
304                 };
305                 break;
306             }
307         }
308 
309         mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
310         mDisableChildrenWhenDisabled = a.getBoolean(
311                 R.styleable.Spinner_disableChildrenWhenDisabled, false);
312 
313         a.recycle();
314 
315         // Base constructor can call setAdapter before we initialize mPopup.
316         // Finish setting things up if this happened.
317         if (mTempAdapter != null) {
318             setAdapter(mTempAdapter);
319             mTempAdapter = null;
320         }
321     }
322 
323     /**
324      * @return the context used to inflate the Spinner's popup or dialog window
325      */
getPopupContext()326     public Context getPopupContext() {
327         return mPopupContext;
328     }
329 
330     /**
331      * 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      */
setPopupBackgroundDrawable(Drawable background)338     public void setPopupBackgroundDrawable(Drawable background) {
339         if (!(mPopup instanceof DropdownPopup)) {
340             Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
341             return;
342         }
343         mPopup.setBackgroundDrawable(background);
344     }
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      *
350      * @param resId Resource ID of a background drawable
351      *
352      * @attr ref android.R.styleable#Spinner_popupBackground
353      */
setPopupBackgroundResource(@rawableRes int resId)354     public void setPopupBackgroundResource(@DrawableRes int resId) {
355         setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
356     }
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      */
366     @InspectableProperty
getPopupBackground()367     public Drawable getPopupBackground() {
368         return mPopup.getBackground();
369     }
370 
371     /**
372      * @hide
373      */
374     @TestApi
isPopupShowing()375     public boolean isPopupShowing() {
376         return (mPopup != null) && mPopup.isShowing();
377     }
378 
379     /**
380      * 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      *
385      * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
386      */
setDropDownVerticalOffset(int pixels)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      *
397      * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
398      */
399     @InspectableProperty
getDropDownVerticalOffset()400     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      *
410      * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
411      */
setDropDownHorizontalOffset(int pixels)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      *
422      * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
423      */
424     @InspectableProperty
getDropDownHorizontalOffset()425     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      */
setDropDownWidth(int pixels)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      */
461     @InspectableProperty
getDropDownWidth()462     public int getDropDownWidth() {
463         return mDropDownWidth;
464     }
465 
466     @Override
setEnabled(boolean enabled)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 
477     /**
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      */
setGravity(int gravity)485     public void setGravity(int gravity) {
486         if (mGravity != gravity) {
487             if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
488                 gravity |= Gravity.START;
489             }
490             mGravity = gravity;
491             requestLayout();
492         }
493     }
494 
495     /**
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      */
501     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
getGravity()502     public int getGravity() {
503         return mGravity;
504     }
505 
506     /**
507      * Sets the {@link SpinnerAdapter} used to provide the data which backs
508      * this Spinner.
509      * <p>
510      * 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
518      * 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
522      * 1. On API {@link Build.VERSION_CODES#LOLLIPOP} and above, attempting to set an
523      * adapter with more than one view type will throw an
524      * {@link IllegalArgumentException}.
525      *
526      * @param adapter the adapter to set
527      *
528      * @see AbsSpinner#setAdapter(SpinnerAdapter)
529      * @throws IllegalArgumentException if the adapter has more than one view
530      *         type
531      */
532     @Override
setAdapter(SpinnerAdapter adapter)533     public void setAdapter(SpinnerAdapter adapter) {
534         // 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 
541         super.setAdapter(adapter);
542 
543         mRecycler.clear();
544 
545         final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
546         if (targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
547                 && adapter != null && adapter.getViewTypeCount() != 1) {
548             throw new IllegalArgumentException("Spinner adapter view type count must be 1");
549         }
550 
551         final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
552         mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
553     }
554 
555     @Override
getBaseline()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) {
562             child = makeView(0, false);
563             mRecycler.put(0, child);
564         }
565 
566         if (child != null) {
567             final int childBaseline = child.getBaseline();
568             return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
569         } else {
570             return -1;
571         }
572     }
573 
574     @Override
onDetachedFromWindow()575     protected void onDetachedFromWindow() {
576         super.onDetachedFromWindow();
577 
578         if (mPopup != null && mPopup.isShowing()) {
579             mPopup.dismiss();
580         }
581     }
582 
583     /**
584      * <p>A spinner does not support item click events. Calling this method
585      * will raise an exception.</p>
586      * <p>Instead use {@link AdapterView#setOnItemSelectedListener}.
587      *
588      * @param l this listener will be ignored
589      */
590     @Override
setOnItemClickListener(OnItemClickListener l)591     public void setOnItemClickListener(OnItemClickListener l) {
592         throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
593     }
594 
595     /**
596      * @hide internal use only
597      */
598     @UnsupportedAppUsage
setOnItemClickListenerInt(OnItemClickListener l)599     public void setOnItemClickListenerInt(OnItemClickListener l) {
600         super.setOnItemClickListener(l);
601     }
602 
603     @Override
onTouchEvent(MotionEvent event)604     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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)613     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();
617             setMeasuredDimension(Math.min(Math.max(measuredWidth,
618                     measureContentWidth(getAdapter(), getBackground())),
619                     MeasureSpec.getSize(widthMeasureSpec)),
620                     getMeasuredHeight());
621         }
622     }
623 
624     /**
625      * @see android.view.View#onLayout(boolean,int,int,int,int)
626      *
627      * Creates and positions all views
628      *
629      */
630     @Override
onLayout(boolean changed, int l, int t, int r, int b)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      *
641      * @param delta Change in the selected position. +1 means selection is moving to the right,
642      * so views are scrolling to the left. -1 means selection is moving to the left.
643      */
644     @Override
layout(int delta, boolean animate)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 
668         // Make selected view and position it
669         mFirstPosition = mSelectedPosition;
670 
671         if (mAdapter != null) {
672             View sel = makeView(mSelectedPosition, true);
673             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);
686         }
687 
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
707      * @param addChild true to add the child to the spinner, false to obtain and configure only.
708      * @return A view for the given position
709      */
makeView(int position, boolean addChild)710     private View makeView(int position, boolean addChild) {
711         View child;
712 
713         if (!mDataChanged) {
714             child = mRecycler.get(position);
715             if (child != null) {
716                 // Position the view
717                 setUpChild(child, addChild);
718 
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
727         setUpChild(child, addChild);
728 
729         return child;
730     }
731 
732     /**
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
737      * @param addChild true if the child should be added to the Spinner during setup
738      */
setUpChild(View child, boolean addChild)739     private void setUpChild(View child, boolean addChild) {
740 
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 
748         addViewInLayout(child, 0, lp);
749 
750         child.setSelected(hasFocus());
751         if (mDisableChildrenWhenDisabled) {
752             child.setEnabled(isEnabled());
753         }
754 
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
769                 + ((getMeasuredHeight() - mSpinnerPadding.bottom -
770                         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);
778 
779         if (!addChild) {
780             removeViewInLayout(child);
781         }
782     }
783 
784     @Override
performClick()785     public boolean performClick() {
786         boolean handled = super.performClick();
787 
788         if (!handled) {
789             handled = true;
790 
791             if (!mPopup.isShowing()) {
792                 mPopup.show(getTextDirection(), getTextAlignment());
793             }
794         }
795 
796         return handled;
797     }
798 
799     @Override
onClick(DialogInterface dialog, int which)800     public void onClick(DialogInterface dialog, int which) {
801         setSelection(which);
802         dialog.dismiss();
803     }
804 
805     @Override
getAccessibilityClassName()806     public CharSequence getAccessibilityClassName() {
807         return Spinner.class.getName();
808     }
809 
810     /** @hide */
811     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)812     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
813         super.onInitializeAccessibilityNodeInfoInternal(info);
814 
815         if (mAdapter != null) {
816             info.setCanOpenPopup(true);
817         }
818     }
819 
820     /**
821      * Sets the prompt to display when the dialog is shown.
822      * @param prompt the prompt to set
823      */
setPrompt(CharSequence prompt)824     public void setPrompt(CharSequence prompt) {
825         mPopup.setPromptText(prompt);
826     }
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      */
setPromptId(int promptId)832     public void setPromptId(int promptId) {
833         setPrompt(getContext().getText(promptId));
834     }
835 
836     /**
837      * @return The prompt to display when the dialog is shown
838      */
839     @InspectableProperty
getPrompt()840     public CharSequence getPrompt() {
841         return mPopup.getHintText();
842     }
843 
measureContentWidth(SpinnerAdapter adapter, Drawable background)844     int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
845         if (adapter == null) {
846             return 0;
847         }
848 
849         int width = 0;
850         View itemView = null;
851         int itemType = 0;
852         final int widthMeasureSpec =
853             MeasureSpec.makeSafeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED);
854         final int heightMeasureSpec =
855             MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED);
856 
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.
859         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++) {
864             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
880         if (background != null) {
881             background.getPadding(mTempRect);
882             width += mTempRect.left + mTempRect.right;
883         }
884 
885         return width;
886     }
887 
888     @Override
onSaveInstanceState()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
onRestoreInstanceState(Parcelable state)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()) {
908                             mPopup.show(getTextDirection(), getTextAlignment());
909                         }
910                         final ViewTreeObserver vto = getViewTreeObserver();
911                         if (vto != null) {
912                             vto.removeOnGlobalLayoutListener(this);
913                         }
914                     }
915                 };
916                 vto.addOnGlobalLayoutListener(listener);
917             }
918         }
919     }
920 
921     @Override
onResolvePointerIcon(MotionEvent event, int pointerIndex)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 
929     static class SavedState extends AbsSpinner.SavedState {
930         boolean showDropdown;
931 
SavedState(Parcelable superState)932         SavedState(Parcelable superState) {
933             super(superState);
934         }
935 
SavedState(Parcel in)936         private SavedState(Parcel in) {
937             super(in);
938             showDropdown = in.readByte() != 0;
939         }
940 
941         @Override
writeToParcel(Parcel out, int flags)942         public void writeToParcel(Parcel out, int flags) {
943             super.writeToParcel(out, flags);
944             out.writeByte((byte) (showDropdown ? 1 : 0));
945         }
946 
947         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
948                 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 
959     /**
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;
965         private ListAdapter mListAdapter;
966 
967         /**
968          * Creates a new ListAdapter wrapper for the specified adapter.
969          *
970          * @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
973          */
DropDownAdapter(@ullable SpinnerAdapter adapter, @Nullable Resources.Theme dropDownTheme)974         public DropDownAdapter(@Nullable SpinnerAdapter adapter,
975                 @Nullable Resources.Theme dropDownTheme) {
976             mAdapter = adapter;
977 
978             if (adapter instanceof ListAdapter) {
979                 mListAdapter = (ListAdapter) adapter;
980             }
981 
982             if (dropDownTheme != null && adapter instanceof ThemedSpinnerAdapter) {
983                 final ThemedSpinnerAdapter themedAdapter = (ThemedSpinnerAdapter) adapter;
984                 if (themedAdapter.getDropDownViewTheme() == null) {
985                     themedAdapter.setDropDownViewTheme(dropDownTheme);
986                 }
987             }
988         }
989 
getCount()990         public int getCount() {
991             return mAdapter == null ? 0 : mAdapter.getCount();
992         }
993 
getItem(int position)994         public Object getItem(int position) {
995             return mAdapter == null ? null : mAdapter.getItem(position);
996         }
997 
getItemId(int position)998         public long getItemId(int position) {
999             return mAdapter == null ? -1 : mAdapter.getItemId(position);
1000         }
1001 
getView(int position, View convertView, ViewGroup parent)1002         public View getView(int position, View convertView, ViewGroup parent) {
1003             return getDropDownView(position, convertView, parent);
1004         }
1005 
getDropDownView(int position, View convertView, ViewGroup parent)1006         public View getDropDownView(int position, View convertView, ViewGroup parent) {
1007             return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
1008         }
1009 
hasStableIds()1010         public boolean hasStableIds() {
1011             return mAdapter != null && mAdapter.hasStableIds();
1012         }
1013 
registerDataSetObserver(DataSetObserver observer)1014         public void registerDataSetObserver(DataSetObserver observer) {
1015             if (mAdapter != null) {
1016                 mAdapter.registerDataSetObserver(observer);
1017             }
1018         }
1019 
unregisterDataSetObserver(DataSetObserver observer)1020         public void unregisterDataSetObserver(DataSetObserver observer) {
1021             if (mAdapter != null) {
1022                 mAdapter.unregisterDataSetObserver(observer);
1023             }
1024         }
1025 
1026         /**
1027          * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
1028          * Otherwise, return true.
1029          */
areAllItemsEnabled()1030         public boolean areAllItemsEnabled() {
1031             final ListAdapter adapter = mListAdapter;
1032             if (adapter != null) {
1033                 return adapter.areAllItemsEnabled();
1034             } else {
1035                 return true;
1036             }
1037         }
1038 
1039         /**
1040          * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
1041          * Otherwise, return true.
1042          */
isEnabled(int position)1043         public boolean isEnabled(int position) {
1044             final ListAdapter adapter = mListAdapter;
1045             if (adapter != null) {
1046                 return adapter.isEnabled(position);
1047             } else {
1048                 return true;
1049             }
1050         }
1051 
getItemViewType(int position)1052         public int getItemViewType(int position) {
1053             return 0;
1054         }
1055 
getViewTypeCount()1056         public int getViewTypeCount() {
1057             return 1;
1058         }
1059 
isEmpty()1060         public boolean isEmpty() {
1061             return getCount() == 0;
1062         }
1063     }
1064 
1065     /**
1066      * Implements some sort of popup selection interface for selecting a spinner option.
1067      * Allows for different spinner modes.
1068      */
1069     private interface SpinnerPopup {
setAdapter(ListAdapter adapter)1070         public void setAdapter(ListAdapter adapter);
1071 
1072         /**
1073          * Show the popup
1074          */
show(int textDirection, int textAlignment)1075         public void show(int textDirection, int textAlignment);
1076 
1077         /**
1078          * Dismiss the popup
1079          */
dismiss()1080         public void dismiss();
1081 
1082         /**
1083          * @return true if the popup is showing, false otherwise.
1084          */
1085         @UnsupportedAppUsage
isShowing()1086         public boolean isShowing();
1087 
1088         /**
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          */
setPromptText(CharSequence hintText)1093         public void setPromptText(CharSequence hintText);
getHintText()1094         public CharSequence getHintText();
1095 
setBackgroundDrawable(Drawable bg)1096         public void setBackgroundDrawable(Drawable bg);
setVerticalOffset(int px)1097         public void setVerticalOffset(int px);
setHorizontalOffset(int px)1098         public void setHorizontalOffset(int px);
getBackground()1099         public Drawable getBackground();
getVerticalOffset()1100         public int getVerticalOffset();
getHorizontalOffset()1101         public int getHorizontalOffset();
1102     }
1103 
1104     private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
1105         private AlertDialog mPopup;
1106         private ListAdapter mListAdapter;
1107         private CharSequence mPrompt;
1108 
dismiss()1109         public void dismiss() {
1110             if (mPopup != null) {
1111                 mPopup.dismiss();
1112                 mPopup = null;
1113             }
1114         }
1115 
1116         @UnsupportedAppUsage
isShowing()1117         public boolean isShowing() {
1118             return mPopup != null ? mPopup.isShowing() : false;
1119         }
1120 
setAdapter(ListAdapter adapter)1121         public void setAdapter(ListAdapter adapter) {
1122             mListAdapter = adapter;
1123         }
1124 
setPromptText(CharSequence hintText)1125         public void setPromptText(CharSequence hintText) {
1126             mPrompt = hintText;
1127         }
1128 
getHintText()1129         public CharSequence getHintText() {
1130             return mPrompt;
1131         }
1132 
show(int textDirection, int textAlignment)1133         public void show(int textDirection, int textAlignment) {
1134             if (mListAdapter == null) {
1135                 return;
1136             }
1137             AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
1138             if (mPrompt != null) {
1139                 builder.setTitle(mPrompt);
1140             }
1141             mPopup = builder.setSingleChoiceItems(mListAdapter,
1142                     getSelectedItemPosition(), this).create();
1143             final ListView listView = mPopup.getListView();
1144             listView.setTextDirection(textDirection);
1145             listView.setTextAlignment(textAlignment);
1146             mPopup.show();
1147         }
1148 
onClick(DialogInterface dialog, int which)1149         public void onClick(DialogInterface dialog, int which) {
1150             setSelection(which);
1151             if (mOnItemClickListener != null) {
1152                 performItemClick(null, which, mListAdapter.getItemId(which));
1153             }
1154             dismiss();
1155         }
1156 
1157         @Override
setBackgroundDrawable(Drawable bg)1158         public void setBackgroundDrawable(Drawable bg) {
1159             Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
1160         }
1161 
1162         @Override
setVerticalOffset(int px)1163         public void setVerticalOffset(int px) {
1164             Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
1165         }
1166 
1167         @Override
setHorizontalOffset(int px)1168         public void setHorizontalOffset(int px) {
1169             Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
1170         }
1171 
1172         @Override
getBackground()1173         public Drawable getBackground() {
1174             return null;
1175         }
1176 
1177         @Override
getVerticalOffset()1178         public int getVerticalOffset() {
1179             return 0;
1180         }
1181 
1182         @Override
getHorizontalOffset()1183         public int getHorizontalOffset() {
1184             return 0;
1185         }
1186     }
1187 
1188     private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
1189         private CharSequence mHintText;
1190         private ListAdapter mAdapter;
1191 
DropdownPopup( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)1192         public DropdownPopup(
1193                 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
1194             super(context, attrs, defStyleAttr, defStyleRes);
1195 
1196             setAnchorView(Spinner.this);
1197             setModal(true);
1198             setPromptPosition(POSITION_PROMPT_ABOVE);
1199             setOnItemClickListener(new OnItemClickListener() {
1200                 public void onItemClick(AdapterView parent, View v, int position, long id) {
1201                     Spinner.this.setSelection(position);
1202                     if (mOnItemClickListener != null) {
1203                         Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
1204                     }
1205                     dismiss();
1206                 }
1207             });
1208         }
1209 
1210         @Override
setAdapter(ListAdapter adapter)1211         public void setAdapter(ListAdapter adapter) {
1212             super.setAdapter(adapter);
1213             mAdapter = adapter;
1214         }
1215 
getHintText()1216         public CharSequence getHintText() {
1217             return mHintText;
1218         }
1219 
setPromptText(CharSequence hintText)1220         public void setPromptText(CharSequence hintText) {
1221             // Hint text is ignored for dropdowns, but maintain it here.
1222             mHintText = hintText;
1223         }
1224 
computeContentWidth()1225         void computeContentWidth() {
1226             final Drawable background = getBackground();
1227             int hOffset = 0;
1228             if (background != null) {
1229                 background.getPadding(mTempRect);
1230                 hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
1231             } else {
1232                 mTempRect.left = mTempRect.right = 0;
1233             }
1234 
1235             final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
1236             final int spinnerPaddingRight = Spinner.this.getPaddingRight();
1237             final int spinnerWidth = Spinner.this.getWidth();
1238 
1239             if (mDropDownWidth == WRAP_CONTENT) {
1240                 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                 }
1247                 setContentWidth(Math.max(
1248                        contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
1249             } else if (mDropDownWidth == MATCH_PARENT) {
1250                 setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
1251             } else {
1252                 setContentWidth(mDropDownWidth);
1253             }
1254 
1255             if (isLayoutRtl()) {
1256                 hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
1257             } else {
1258                 hOffset += spinnerPaddingLeft;
1259             }
1260             setHorizontalOffset(hOffset);
1261         }
1262 
show(int textDirection, int textAlignment)1263         public void show(int textDirection, int textAlignment) {
1264             final boolean wasShowing = isShowing();
1265 
1266             computeContentWidth();
1267 
1268             setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
1269             super.show();
1270             final ListView listView = getListView();
1271             listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1272             listView.setTextDirection(textDirection);
1273             listView.setTextAlignment(textAlignment);
1274             setSelection(Spinner.this.getSelectedItemPosition());
1275 
1276             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 
1282             // 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();
1292                         } 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();
1298                         }
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             }
1311         }
1312     }
1313 
1314 }
1315