• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 com.android.internal.R;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.database.DataSetObserver;
24 import android.graphics.Rect;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.util.AttributeSet;
28 import android.util.SparseArray;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.animation.Interpolator;
32 
33 
34 /**
35  * An abstract base class for spinner widgets. SDK users will probably not
36  * need to use this class.
37  *
38  * @attr ref android.R.styleable#AbsSpinner_entries
39  */
40 public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
41 
42     SpinnerAdapter mAdapter;
43 
44     int mHeightMeasureSpec;
45     int mWidthMeasureSpec;
46     boolean mBlockLayoutRequests;
47     int mSelectionLeftPadding = 0;
48     int mSelectionTopPadding = 0;
49     int mSelectionRightPadding = 0;
50     int mSelectionBottomPadding = 0;
51     Rect mSpinnerPadding = new Rect();
52     View mSelectedView = null;
53     Interpolator mInterpolator;
54 
55     RecycleBin mRecycler = new RecycleBin();
56     private DataSetObserver mDataSetObserver;
57 
58 
59     /** Temporary frame to hold a child View's frame rectangle */
60     private Rect mTouchFrame;
61 
AbsSpinner(Context context)62     public AbsSpinner(Context context) {
63         super(context);
64         initAbsSpinner();
65     }
66 
AbsSpinner(Context context, AttributeSet attrs)67     public AbsSpinner(Context context, AttributeSet attrs) {
68         this(context, attrs, 0);
69     }
70 
AbsSpinner(Context context, AttributeSet attrs, int defStyle)71     public AbsSpinner(Context context, AttributeSet attrs, int defStyle) {
72         super(context, attrs, defStyle);
73         initAbsSpinner();
74 
75         TypedArray a = context.obtainStyledAttributes(attrs,
76                 com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
77 
78         CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
79         if (entries != null) {
80             ArrayAdapter<CharSequence> adapter =
81                     new ArrayAdapter<CharSequence>(context,
82                             R.layout.simple_spinner_item, entries);
83             adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
84             setAdapter(adapter);
85         }
86 
87         a.recycle();
88     }
89 
90     /**
91      * Common code for different constructor flavors
92      */
initAbsSpinner()93     private void initAbsSpinner() {
94         setFocusable(true);
95         setWillNotDraw(false);
96     }
97 
98 
99     /**
100      * The Adapter is used to provide the data which backs this Spinner.
101      * It also provides methods to transform spinner items based on their position
102      * relative to the selected item.
103      * @param adapter The SpinnerAdapter to use for this Spinner
104      */
105     @Override
setAdapter(SpinnerAdapter adapter)106     public void setAdapter(SpinnerAdapter adapter) {
107         if (null != mAdapter) {
108             mAdapter.unregisterDataSetObserver(mDataSetObserver);
109             resetList();
110         }
111 
112         mAdapter = adapter;
113 
114         mOldSelectedPosition = INVALID_POSITION;
115         mOldSelectedRowId = INVALID_ROW_ID;
116 
117         if (mAdapter != null) {
118             mOldItemCount = mItemCount;
119             mItemCount = mAdapter.getCount();
120             checkFocus();
121 
122             mDataSetObserver = new AdapterDataSetObserver();
123             mAdapter.registerDataSetObserver(mDataSetObserver);
124 
125             int position = mItemCount > 0 ? 0 : INVALID_POSITION;
126 
127             setSelectedPositionInt(position);
128             setNextSelectedPositionInt(position);
129 
130             if (mItemCount == 0) {
131                 // Nothing selected
132                 checkSelectionChanged();
133             }
134 
135         } else {
136             checkFocus();
137             resetList();
138             // Nothing selected
139             checkSelectionChanged();
140         }
141 
142         requestLayout();
143     }
144 
145     /**
146      * Clear out all children from the list
147      */
resetList()148     void resetList() {
149         mDataChanged = false;
150         mNeedSync = false;
151 
152         removeAllViewsInLayout();
153         mOldSelectedPosition = INVALID_POSITION;
154         mOldSelectedRowId = INVALID_ROW_ID;
155 
156         setSelectedPositionInt(INVALID_POSITION);
157         setNextSelectedPositionInt(INVALID_POSITION);
158         invalidate();
159     }
160 
161     /**
162      * @see android.view.View#measure(int, int)
163      *
164      * Figure out the dimensions of this Spinner. The width comes from
165      * the widthMeasureSpec as Spinnners can't have their width set to
166      * UNSPECIFIED. The height is based on the height of the selected item
167      * plus padding.
168      */
169     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)170     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
171         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
172         int widthSize;
173         int heightSize;
174 
175         mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
176                 : mSelectionLeftPadding;
177         mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
178                 : mSelectionTopPadding;
179         mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
180                 : mSelectionRightPadding;
181         mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
182                 : mSelectionBottomPadding;
183 
184         if (mDataChanged) {
185             handleDataChanged();
186         }
187 
188         int preferredHeight = 0;
189         int preferredWidth = 0;
190         boolean needsMeasuring = true;
191 
192         int selectedPosition = getSelectedItemPosition();
193         if (selectedPosition >= 0 && mAdapter != null) {
194             // Try looking in the recycler. (Maybe we were measured once already)
195             View view = mRecycler.get(selectedPosition);
196             if (view == null) {
197                 // Make a new one
198                 view = mAdapter.getView(selectedPosition, null, this);
199             }
200 
201             if (view != null) {
202                 // Put in recycler for re-measuring and/or layout
203                 mRecycler.put(selectedPosition, view);
204             }
205 
206             if (view != null) {
207                 if (view.getLayoutParams() == null) {
208                     mBlockLayoutRequests = true;
209                     view.setLayoutParams(generateDefaultLayoutParams());
210                     mBlockLayoutRequests = false;
211                 }
212                 measureChild(view, widthMeasureSpec, heightMeasureSpec);
213 
214                 preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
215                 preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
216 
217                 needsMeasuring = false;
218             }
219         }
220 
221         if (needsMeasuring) {
222             // No views -- just use padding
223             preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
224             if (widthMode == MeasureSpec.UNSPECIFIED) {
225                 preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
226             }
227         }
228 
229         preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
230         preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
231 
232         heightSize = resolveSize(preferredHeight, heightMeasureSpec);
233         widthSize = resolveSize(preferredWidth, widthMeasureSpec);
234 
235         setMeasuredDimension(widthSize, heightSize);
236         mHeightMeasureSpec = heightMeasureSpec;
237         mWidthMeasureSpec = widthMeasureSpec;
238     }
239 
240 
getChildHeight(View child)241     int getChildHeight(View child) {
242         return child.getMeasuredHeight();
243     }
244 
getChildWidth(View child)245     int getChildWidth(View child) {
246         return child.getMeasuredWidth();
247     }
248 
249     @Override
generateDefaultLayoutParams()250     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
251         return new ViewGroup.LayoutParams(
252                 ViewGroup.LayoutParams.FILL_PARENT,
253                 ViewGroup.LayoutParams.WRAP_CONTENT);
254     }
255 
recycleAllViews()256     void recycleAllViews() {
257         int childCount = getChildCount();
258         final AbsSpinner.RecycleBin recycleBin = mRecycler;
259 
260         // All views go in recycler
261         for (int i=0; i<childCount; i++) {
262             View v = getChildAt(i);
263             int index = mFirstPosition + i;
264             recycleBin.put(index, v);
265         }
266     }
267 
268     @Override
handleDataChanged()269     void handleDataChanged() {
270         // FIXME -- this is called from both measure and layout.
271         // This is harmless right now, but we don't want to do redundant work if
272         // this gets more complicated
273        super.handleDataChanged();
274     }
275 
276 
277 
278     /**
279      * Jump directly to a specific item in the adapter data.
280      */
setSelection(int position, boolean animate)281     public void setSelection(int position, boolean animate) {
282         // Animate only if requested position is already on screen somewhere
283         boolean shouldAnimate = animate && mFirstPosition <= position &&
284                 position <= mFirstPosition + getChildCount() - 1;
285         setSelectionInt(position, shouldAnimate);
286     }
287 
288 
289     @Override
setSelection(int position)290     public void setSelection(int position) {
291         setNextSelectedPositionInt(position);
292         requestLayout();
293         invalidate();
294     }
295 
296 
297     /**
298      * Makes the item at the supplied position selected.
299      *
300      * @param position Position to select
301      * @param animate Should the transition be animated
302      *
303      */
setSelectionInt(int position, boolean animate)304     void setSelectionInt(int position, boolean animate) {
305         if (position != mOldSelectedPosition) {
306             mBlockLayoutRequests = true;
307             int delta  = position - mSelectedPosition;
308             setNextSelectedPositionInt(position);
309             layout(delta, animate);
310             mBlockLayoutRequests = false;
311         }
312     }
313 
layout(int delta, boolean animate)314     abstract void layout(int delta, boolean animate);
315 
316     @Override
getSelectedView()317     public View getSelectedView() {
318         if (mItemCount > 0 && mSelectedPosition >= 0) {
319             return getChildAt(mSelectedPosition - mFirstPosition);
320         } else {
321             return null;
322         }
323     }
324 
325     /**
326      * Override to prevent spamming ourselves with layout requests
327      * as we place views
328      *
329      * @see android.view.View#requestLayout()
330      */
331     @Override
requestLayout()332     public void requestLayout() {
333         if (!mBlockLayoutRequests) {
334             super.requestLayout();
335         }
336     }
337 
338 
339 
340     @Override
getAdapter()341     public SpinnerAdapter getAdapter() {
342         return mAdapter;
343     }
344 
345     @Override
getCount()346     public int getCount() {
347         return mItemCount;
348     }
349 
350     /**
351      * Maps a point to a position in the list.
352      *
353      * @param x X in local coordinate
354      * @param y Y in local coordinate
355      * @return The position of the item which contains the specified point, or
356      *         {@link #INVALID_POSITION} if the point does not intersect an item.
357      */
pointToPosition(int x, int y)358     public int pointToPosition(int x, int y) {
359         Rect frame = mTouchFrame;
360         if (frame == null) {
361             mTouchFrame = new Rect();
362             frame = mTouchFrame;
363         }
364 
365         final int count = getChildCount();
366         for (int i = count - 1; i >= 0; i--) {
367             View child = getChildAt(i);
368             if (child.getVisibility() == View.VISIBLE) {
369                 child.getHitRect(frame);
370                 if (frame.contains(x, y)) {
371                     return mFirstPosition + i;
372                 }
373             }
374         }
375         return INVALID_POSITION;
376     }
377 
378     static class SavedState extends BaseSavedState {
379         long selectedId;
380         int position;
381 
382         /**
383          * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
384          */
SavedState(Parcelable superState)385         SavedState(Parcelable superState) {
386             super(superState);
387         }
388 
389         /**
390          * Constructor called from {@link #CREATOR}
391          */
SavedState(Parcel in)392         private SavedState(Parcel in) {
393             super(in);
394             selectedId = in.readLong();
395             position = in.readInt();
396         }
397 
398         @Override
writeToParcel(Parcel out, int flags)399         public void writeToParcel(Parcel out, int flags) {
400             super.writeToParcel(out, flags);
401             out.writeLong(selectedId);
402             out.writeInt(position);
403         }
404 
405         @Override
toString()406         public String toString() {
407             return "AbsSpinner.SavedState{"
408                     + Integer.toHexString(System.identityHashCode(this))
409                     + " selectedId=" + selectedId
410                     + " position=" + position + "}";
411         }
412 
413         public static final Parcelable.Creator<SavedState> CREATOR
414                 = new Parcelable.Creator<SavedState>() {
415             public SavedState createFromParcel(Parcel in) {
416                 return new SavedState(in);
417             }
418 
419             public SavedState[] newArray(int size) {
420                 return new SavedState[size];
421             }
422         };
423     }
424 
425     @Override
onSaveInstanceState()426     public Parcelable onSaveInstanceState() {
427         Parcelable superState = super.onSaveInstanceState();
428         SavedState ss = new SavedState(superState);
429         ss.selectedId = getSelectedItemId();
430         if (ss.selectedId >= 0) {
431             ss.position = getSelectedItemPosition();
432         } else {
433             ss.position = INVALID_POSITION;
434         }
435         return ss;
436     }
437 
438     @Override
onRestoreInstanceState(Parcelable state)439     public void onRestoreInstanceState(Parcelable state) {
440         SavedState ss = (SavedState) state;
441 
442         super.onRestoreInstanceState(ss.getSuperState());
443 
444         if (ss.selectedId >= 0) {
445             mDataChanged = true;
446             mNeedSync = true;
447             mSyncRowId = ss.selectedId;
448             mSyncPosition = ss.position;
449             mSyncMode = SYNC_SELECTED_POSITION;
450             requestLayout();
451         }
452     }
453 
454     class RecycleBin {
455         private SparseArray<View> mScrapHeap = new SparseArray<View>();
456 
put(int position, View v)457         public void put(int position, View v) {
458             mScrapHeap.put(position, v);
459         }
460 
get(int position)461         View get(int position) {
462             // System.out.print("Looking for " + position);
463             View result = mScrapHeap.get(position);
464             if (result != null) {
465                 // System.out.println(" HIT");
466                 mScrapHeap.delete(position);
467             } else {
468                 // System.out.println(" MISS");
469             }
470             return result;
471         }
472 
peek(int position)473         View peek(int position) {
474             // System.out.print("Looking for " + position);
475             return mScrapHeap.get(position);
476         }
477 
clear()478         void clear() {
479             final SparseArray<View> scrapHeap = mScrapHeap;
480             final int count = scrapHeap.size();
481             for (int i = 0; i < count; i++) {
482                 final View view = scrapHeap.valueAt(i);
483                 if (view != null) {
484                     removeDetachedView(view, true);
485                 }
486             }
487             scrapHeap.clear();
488         }
489     }
490 }
491