Home | History | Annotate | Download | only in widget
      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.UnsupportedAppUsage;
     23 import android.annotation.Widget;
     24 import android.app.AlertDialog;
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    367     public Drawable getPopupBackground() {
    368         return mPopup.getBackground();
    369     }
    370 
    371     /**
    372      * @hide
    373      */
    374     @TestApi
    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      */
    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
    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      */
    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
    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      */
    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
    462     public int getDropDownWidth() {
    463         return mDropDownWidth;
    464     }
    465 
    466     @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 
    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      */
    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)
    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
    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
    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
    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
    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
    599     public void setOnItemClickListenerInt(OnItemClickListener l) {
    600         super.setOnItemClickListener(l);
    601     }
    602 
    603     @Override
    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
    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
    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
    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      */
    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      */
    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
    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
    800     public void onClick(DialogInterface dialog, int which) {
    801         setSelection(which);
    802         dialog.dismiss();
    803     }
    804 
    805     @Override
    806     public CharSequence getAccessibilityClassName() {
    807         return Spinner.class.getName();
    808     }
    809 
    810     /** @hide */
    811     @Override
    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      */
    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      */
    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
    840     public CharSequence getPrompt() {
    841         return mPopup.getHintText();
    842     }
    843 
    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
    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()) {
    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
    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 
    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 
    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          */
    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 
    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) {
   1007             return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
   1008         }
   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         /**
   1027          * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
   1028          * Otherwise, return true.
   1029          */
   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          */
   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 
   1052         public int getItemViewType(int position) {
   1053             return 0;
   1054         }
   1055 
   1056         public int getViewTypeCount() {
   1057             return 1;
   1058         }
   1059 
   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 {
   1070         public void setAdapter(ListAdapter adapter);
   1071 
   1072         /**
   1073          * Show the popup
   1074          */
   1075         public void show(int textDirection, int textAlignment);
   1076 
   1077         /**
   1078          * Dismiss the popup
   1079          */
   1080         public void dismiss();
   1081 
   1082         /**
   1083          * @return true if the popup is showing, false otherwise.
   1084          */
   1085         @UnsupportedAppUsage
   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          */
   1093         public void setPromptText(CharSequence hintText);
   1094         public CharSequence getHintText();
   1095 
   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();
   1102     }
   1103 
   1104     private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
   1105         private AlertDialog mPopup;
   1106         private ListAdapter mListAdapter;
   1107         private CharSequence mPrompt;
   1108 
   1109         public void dismiss() {
   1110             if (mPopup != null) {
   1111                 mPopup.dismiss();
   1112                 mPopup = null;
   1113             }
   1114         }
   1115 
   1116         @UnsupportedAppUsage
   1117         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         }
   1128 
   1129         public CharSequence getHintText() {
   1130             return mPrompt;
   1131         }
   1132 
   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 
   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
   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         }
   1186     }
   1187 
   1188     private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
   1189         private CharSequence mHintText;
   1190         private ListAdapter mAdapter;
   1191 
   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
   1211         public void setAdapter(ListAdapter adapter) {
   1212             super.setAdapter(adapter);
   1213             mAdapter = adapter;
   1214         }
   1215 
   1216         public CharSequence getHintText() {
   1217             return mHintText;
   1218         }
   1219 
   1220         public void setPromptText(CharSequence hintText) {
   1221             // Hint text is ignored for dropdowns, but maintain it here.
   1222             mHintText = hintText;
   1223         }
   1224 
   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 
   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