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