• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Widget;
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.DialogInterface.OnClickListener;
24 import android.content.res.TypedArray;
25 import android.database.DataSetObserver;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.ViewGroup;
29 
30 
31 /**
32  * A view that displays one child at a time and lets the user pick among them.
33  * The items in the Spinner come from the {@link Adapter} associated with
34  * this view.
35  *
36  * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
37  * tutorial</a>.</p>
38  *
39  * @attr ref android.R.styleable#Spinner_prompt
40  */
41 @Widget
42 public class Spinner extends AbsSpinner implements OnClickListener {
43 
44     private CharSequence mPrompt;
45     private AlertDialog mPopup;
46 
Spinner(Context context)47     public Spinner(Context context) {
48         this(context, null);
49     }
50 
Spinner(Context context, AttributeSet attrs)51     public Spinner(Context context, AttributeSet attrs) {
52         this(context, attrs, com.android.internal.R.attr.spinnerStyle);
53     }
54 
Spinner(Context context, AttributeSet attrs, int defStyle)55     public Spinner(Context context, AttributeSet attrs, int defStyle) {
56         super(context, attrs, defStyle);
57 
58         TypedArray a = context.obtainStyledAttributes(attrs,
59                 com.android.internal.R.styleable.Spinner, defStyle, 0);
60 
61         mPrompt = a.getString(com.android.internal.R.styleable.Spinner_prompt);
62 
63         a.recycle();
64     }
65 
66     @Override
getBaseline()67     public int getBaseline() {
68         View child = null;
69 
70         if (getChildCount() > 0) {
71             child = getChildAt(0);
72         } else if (mAdapter != null && mAdapter.getCount() > 0) {
73             child = makeAndAddView(0);
74             // TODO: We should probably put the child in the recycler
75         }
76 
77         if (child != null) {
78             return child.getTop() + child.getBaseline();
79         } else {
80             return -1;
81         }
82     }
83 
84     @Override
onDetachedFromWindow()85     protected void onDetachedFromWindow() {
86         super.onDetachedFromWindow();
87 
88         if (mPopup != null && mPopup.isShowing()) {
89             mPopup.dismiss();
90             mPopup = null;
91         }
92     }
93 
94     /**
95      * <p>A spinner does not support item click events. Calling this method
96      * will raise an exception.</p>
97      *
98      * @param l this listener will be ignored
99      */
100     @Override
setOnItemClickListener(OnItemClickListener l)101     public void setOnItemClickListener(OnItemClickListener l) {
102         throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
103     }
104 
105     /**
106      * @see android.view.View#onLayout(boolean,int,int,int,int)
107      *
108      * Creates and positions all views
109      *
110      */
111     @Override
onLayout(boolean changed, int l, int t, int r, int b)112     protected void onLayout(boolean changed, int l, int t, int r, int b) {
113         super.onLayout(changed, l, t, r, b);
114         mInLayout = true;
115         layout(0, false);
116         mInLayout = false;
117     }
118 
119     /**
120      * Creates and positions all views for this Spinner.
121      *
122      * @param delta Change in the selected position. +1 moves selection is moving to the right,
123      * so views are scrolling to the left. -1 means selection is moving to the left.
124      */
125     @Override
layout(int delta, boolean animate)126     void layout(int delta, boolean animate) {
127         int childrenLeft = mSpinnerPadding.left;
128         int childrenWidth = mRight - mLeft - mSpinnerPadding.left - mSpinnerPadding.right;
129 
130         if (mDataChanged) {
131             handleDataChanged();
132         }
133 
134         // Handle the empty set by removing all views
135         if (mItemCount == 0) {
136             resetList();
137             return;
138         }
139 
140         if (mNextSelectedPosition >= 0) {
141             setSelectedPositionInt(mNextSelectedPosition);
142         }
143 
144         recycleAllViews();
145 
146         // Clear out old views
147         removeAllViewsInLayout();
148 
149         // Make selected view and center it
150         mFirstPosition = mSelectedPosition;
151         View sel = makeAndAddView(mSelectedPosition);
152         int width = sel.getMeasuredWidth();
153         int selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
154         sel.offsetLeftAndRight(selectedOffset);
155 
156         // Flush any cached views that did not get reused above
157         mRecycler.clear();
158 
159         invalidate();
160 
161         checkSelectionChanged();
162 
163         mDataChanged = false;
164         mNeedSync = false;
165         setNextSelectedPositionInt(mSelectedPosition);
166     }
167 
168     /**
169      * Obtain a view, either by pulling an existing view from the recycler or
170      * by getting a new one from the adapter. If we are animating, make sure
171      * there is enough information in the view's layout parameters to animate
172      * from the old to new positions.
173      *
174      * @param position Position in the spinner for the view to obtain
175      * @return A view that has been added to the spinner
176      */
makeAndAddView(int position)177     private View makeAndAddView(int position) {
178 
179         View child;
180 
181         if (!mDataChanged) {
182             child = mRecycler.get(position);
183             if (child != null) {
184                 // Position the view
185                 setUpChild(child);
186 
187                 return child;
188             }
189         }
190 
191         // Nothing found in the recycler -- ask the adapter for a view
192         child = mAdapter.getView(position, null, this);
193 
194         // Position the view
195         setUpChild(child);
196 
197         return child;
198     }
199 
200 
201 
202     /**
203      * Helper for makeAndAddView to set the position of a view
204      * and fill out its layout paramters.
205      *
206      * @param child The view to position
207      */
setUpChild(View child)208     private void setUpChild(View child) {
209 
210         // Respect layout params that are already in the view. Otherwise
211         // make some up...
212         ViewGroup.LayoutParams lp = child.getLayoutParams();
213         if (lp == null) {
214             lp = generateDefaultLayoutParams();
215         }
216 
217         addViewInLayout(child, 0, lp);
218 
219         child.setSelected(hasFocus());
220 
221         // Get measure specs
222         int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
223                 mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
224         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
225                 mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
226 
227         // Measure child
228         child.measure(childWidthSpec, childHeightSpec);
229 
230         int childLeft;
231         int childRight;
232 
233         // Position vertically based on gravity setting
234         int childTop = mSpinnerPadding.top
235                 + ((mMeasuredHeight - mSpinnerPadding.bottom -
236                         mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
237         int childBottom = childTop + child.getMeasuredHeight();
238 
239         int width = child.getMeasuredWidth();
240         childLeft = 0;
241         childRight = childLeft + width;
242 
243         child.layout(childLeft, childTop, childRight, childBottom);
244     }
245 
246     @Override
performClick()247     public boolean performClick() {
248         boolean handled = super.performClick();
249 
250         if (!handled) {
251             handled = true;
252             Context context = getContext();
253 
254             final DropDownAdapter adapter = new DropDownAdapter(getAdapter());
255 
256             AlertDialog.Builder builder = new AlertDialog.Builder(context);
257             if (mPrompt != null) {
258                 builder.setTitle(mPrompt);
259             }
260             mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show();
261         }
262 
263         return handled;
264     }
265 
onClick(DialogInterface dialog, int which)266     public void onClick(DialogInterface dialog, int which) {
267         setSelection(which);
268         dialog.dismiss();
269         mPopup = null;
270     }
271 
272     /**
273      * Sets the prompt to display when the dialog is shown.
274      * @param prompt the prompt to set
275      */
setPrompt(CharSequence prompt)276     public void setPrompt(CharSequence prompt) {
277         mPrompt = prompt;
278     }
279 
280     /**
281      * Sets the prompt to display when the dialog is shown.
282      * @param promptId the resource ID of the prompt to display when the dialog is shown
283      */
setPromptId(int promptId)284     public void setPromptId(int promptId) {
285         mPrompt = getContext().getText(promptId);
286     }
287 
288     /**
289      * @return The prompt to display when the dialog is shown
290      */
getPrompt()291     public CharSequence getPrompt() {
292         return mPrompt;
293     }
294 
295     /**
296      * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
297      * into a ListAdapter.</p>
298      */
299     private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
300         private SpinnerAdapter mAdapter;
301         private ListAdapter mListAdapter;
302 
303         /**
304          * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
305          *
306          * @param adapter the Adapter to transform into a ListAdapter
307          */
DropDownAdapter(SpinnerAdapter adapter)308         public DropDownAdapter(SpinnerAdapter adapter) {
309             this.mAdapter = adapter;
310             if (adapter instanceof ListAdapter) {
311                 this.mListAdapter = (ListAdapter) adapter;
312             }
313         }
314 
getCount()315         public int getCount() {
316             return mAdapter == null ? 0 : mAdapter.getCount();
317         }
318 
getItem(int position)319         public Object getItem(int position) {
320             return mAdapter == null ? null : mAdapter.getItem(position);
321         }
322 
getItemId(int position)323         public long getItemId(int position) {
324             return mAdapter == null ? -1 : mAdapter.getItemId(position);
325         }
326 
getView(int position, View convertView, ViewGroup parent)327         public View getView(int position, View convertView, ViewGroup parent) {
328             return getDropDownView(position, convertView, parent);
329         }
330 
getDropDownView(int position, View convertView, ViewGroup parent)331         public View getDropDownView(int position, View convertView, ViewGroup parent) {
332             return mAdapter == null ? null :
333                     mAdapter.getDropDownView(position, convertView, parent);
334         }
335 
hasStableIds()336         public boolean hasStableIds() {
337             return mAdapter != null && mAdapter.hasStableIds();
338         }
339 
registerDataSetObserver(DataSetObserver observer)340         public void registerDataSetObserver(DataSetObserver observer) {
341             if (mAdapter != null) {
342                 mAdapter.registerDataSetObserver(observer);
343             }
344         }
345 
unregisterDataSetObserver(DataSetObserver observer)346         public void unregisterDataSetObserver(DataSetObserver observer) {
347             if (mAdapter != null) {
348                 mAdapter.unregisterDataSetObserver(observer);
349             }
350         }
351 
352         /**
353          * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
354          * Otherwise, return true.
355          */
areAllItemsEnabled()356         public boolean areAllItemsEnabled() {
357             final ListAdapter adapter = mListAdapter;
358             if (adapter != null) {
359                 return adapter.areAllItemsEnabled();
360             } else {
361                 return true;
362             }
363         }
364 
365         /**
366          * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
367          * Otherwise, return true.
368          */
isEnabled(int position)369         public boolean isEnabled(int position) {
370             final ListAdapter adapter = mListAdapter;
371             if (adapter != null) {
372                 return adapter.isEnabled(position);
373             } else {
374                 return true;
375             }
376         }
377 
getItemViewType(int position)378         public int getItemViewType(int position) {
379             return 0;
380         }
381 
getViewTypeCount()382         public int getViewTypeCount() {
383             return 1;
384         }
385 
isEmpty()386         public boolean isEmpty() {
387             return getCount() == 0;
388         }
389     }
390 }
391