2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.actionbarsherlock
.internal
.widget
;
19 import android
.content
.Context
;
20 import android
.database
.DataSetObserver
;
21 import android
.graphics
.Rect
;
22 import android
.os
.Build
;
23 import android
.os
.Parcel
;
24 import android
.os
.Parcelable
;
25 import android
.util
.AttributeSet
;
26 import android
.util
.SparseArray
;
27 import android
.view
.View
;
28 import android
.view
.ViewGroup
;
29 import android
.widget
.SpinnerAdapter
;
32 * An abstract base class for spinner widgets. SDK users will probably not
33 * need to use this class.
35 * @attr ref android.R.styleable#AbsSpinner_entries
37 public abstract class IcsAbsSpinner
extends IcsAdapterView
<SpinnerAdapter
> {
38 private static final boolean IS_HONEYCOMB
= Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.HONEYCOMB
;
40 SpinnerAdapter mAdapter
;
42 int mHeightMeasureSpec
;
43 int mWidthMeasureSpec
;
44 boolean mBlockLayoutRequests
;
46 int mSelectionLeftPadding
= 0;
47 int mSelectionTopPadding
= 0;
48 int mSelectionRightPadding
= 0;
49 int mSelectionBottomPadding
= 0;
50 final Rect mSpinnerPadding
= new Rect();
52 final RecycleBin mRecycler
= new RecycleBin();
53 private DataSetObserver mDataSetObserver
;
55 /** Temporary frame to hold a child View's frame rectangle */
56 private Rect mTouchFrame
;
58 public IcsAbsSpinner(Context context
) {
63 public IcsAbsSpinner(Context context
, AttributeSet attrs
) {
64 this(context
, attrs
, 0);
67 public IcsAbsSpinner(Context context
, AttributeSet attrs
, int defStyle
) {
68 super(context
, attrs
, defStyle
);
72 TypedArray a = context.obtainStyledAttributes(attrs,
73 com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
75 CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
76 if (entries != null) {
77 ArrayAdapter<CharSequence> adapter =
78 new ArrayAdapter<CharSequence>(context,
79 R.layout.simple_spinner_item, entries);
80 adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
89 * Common code for different constructor flavors
91 private void initAbsSpinner() {
93 setWillNotDraw(false
);
97 * The Adapter is used to provide the data which backs this Spinner.
98 * It also provides methods to transform spinner items based on their position
99 * relative to the selected item.
100 * @param adapter The SpinnerAdapter to use for this Spinner
103 public void setAdapter(SpinnerAdapter adapter
) {
104 if (null
!= mAdapter
) {
105 mAdapter
.unregisterDataSetObserver(mDataSetObserver
);
111 mOldSelectedPosition
= INVALID_POSITION
;
112 mOldSelectedRowId
= INVALID_ROW_ID
;
114 if (mAdapter
!= null
) {
115 mOldItemCount
= mItemCount
;
116 mItemCount
= mAdapter
.getCount();
119 mDataSetObserver
= new AdapterDataSetObserver();
120 mAdapter
.registerDataSetObserver(mDataSetObserver
);
122 int position
= mItemCount
> 0 ?
0 : INVALID_POSITION
;
124 setSelectedPositionInt(position
);
125 setNextSelectedPositionInt(position
);
127 if (mItemCount
== 0) {
129 checkSelectionChanged();
136 checkSelectionChanged();
143 * Clear out all children from the list
146 mDataChanged
= false
;
149 removeAllViewsInLayout();
150 mOldSelectedPosition
= INVALID_POSITION
;
151 mOldSelectedRowId
= INVALID_ROW_ID
;
153 setSelectedPositionInt(INVALID_POSITION
);
154 setNextSelectedPositionInt(INVALID_POSITION
);
159 * @see android.view.View#measure(int, int)
161 * Figure out the dimensions of this Spinner. The width comes from
162 * the widthMeasureSpec as Spinnners can't have their width set to
163 * UNSPECIFIED. The height is based on the height of the selected item
167 protected void onMeasure(int widthMeasureSpec
, int heightMeasureSpec
) {
168 int widthMode
= MeasureSpec
.getMode(widthMeasureSpec
);
172 final int mPaddingLeft
= getPaddingLeft();
173 final int mPaddingTop
= getPaddingTop();
174 final int mPaddingRight
= getPaddingRight();
175 final int mPaddingBottom
= getPaddingBottom();
177 mSpinnerPadding
.left
= mPaddingLeft
> mSelectionLeftPadding ? mPaddingLeft
178 : mSelectionLeftPadding
;
179 mSpinnerPadding
.top
= mPaddingTop
> mSelectionTopPadding ? mPaddingTop
180 : mSelectionTopPadding
;
181 mSpinnerPadding
.right
= mPaddingRight
> mSelectionRightPadding ? mPaddingRight
182 : mSelectionRightPadding
;
183 mSpinnerPadding
.bottom
= mPaddingBottom
> mSelectionBottomPadding ? mPaddingBottom
184 : mSelectionBottomPadding
;
190 int preferredHeight
= 0;
191 int preferredWidth
= 0;
192 boolean needsMeasuring
= true
;
194 int selectedPosition
= getSelectedItemPosition();
195 if (selectedPosition
>= 0 && mAdapter
!= null
&& selectedPosition
< mAdapter
.getCount()) {
196 // Try looking in the recycler. (Maybe we were measured once already)
197 View view
= mRecycler
.get(selectedPosition
);
200 view
= mAdapter
.getView(selectedPosition
, null
, this);
204 // Put in recycler for re-measuring and/or layout
205 mRecycler
.put(selectedPosition
, view
);
209 if (view
.getLayoutParams() == null
) {
210 mBlockLayoutRequests
= true
;
211 view
.setLayoutParams(generateDefaultLayoutParams());
212 mBlockLayoutRequests
= false
;
214 measureChild(view
, widthMeasureSpec
, heightMeasureSpec
);
216 preferredHeight
= getChildHeight(view
) + mSpinnerPadding
.top
+ mSpinnerPadding
.bottom
;
217 preferredWidth
= getChildWidth(view
) + mSpinnerPadding
.left
+ mSpinnerPadding
.right
;
219 needsMeasuring
= false
;
223 if (needsMeasuring
) {
224 // No views -- just use padding
225 preferredHeight
= mSpinnerPadding
.top
+ mSpinnerPadding
.bottom
;
226 if (widthMode
== MeasureSpec
.UNSPECIFIED
) {
227 preferredWidth
= mSpinnerPadding
.left
+ mSpinnerPadding
.right
;
231 preferredHeight
= Math
.max(preferredHeight
, getSuggestedMinimumHeight());
232 preferredWidth
= Math
.max(preferredWidth
, getSuggestedMinimumWidth());
235 heightSize
= resolveSizeAndState(preferredHeight
, heightMeasureSpec
, 0);
236 widthSize
= resolveSizeAndState(preferredWidth
, widthMeasureSpec
, 0);
238 heightSize
= resolveSize(preferredHeight
, heightMeasureSpec
);
239 widthSize
= resolveSize(preferredWidth
, widthMeasureSpec
);
242 setMeasuredDimension(widthSize
, heightSize
);
243 mHeightMeasureSpec
= heightMeasureSpec
;
244 mWidthMeasureSpec
= widthMeasureSpec
;
247 int getChildHeight(View child
) {
248 return child
.getMeasuredHeight();
251 int getChildWidth(View child
) {
252 return child
.getMeasuredWidth();
256 protected ViewGroup
.LayoutParams
generateDefaultLayoutParams() {
257 return new ViewGroup
.LayoutParams(
258 ViewGroup
.LayoutParams
.MATCH_PARENT
,
259 ViewGroup
.LayoutParams
.WRAP_CONTENT
);
262 void recycleAllViews() {
263 final int childCount
= getChildCount();
264 final IcsAbsSpinner
.RecycleBin recycleBin
= mRecycler
;
265 final int position
= mFirstPosition
;
267 // All views go in recycler
268 for (int i
= 0; i
< childCount
; i
++) {
269 View v
= getChildAt(i
);
270 int index
= position
+ i
;
271 recycleBin
.put(index
, v
);
276 * Jump directly to a specific item in the adapter data.
278 public void setSelection(int position
, boolean animate
) {
279 // Animate only if requested position is already on screen somewhere
280 boolean shouldAnimate
= animate
&& mFirstPosition
<= position
&&
281 position
<= mFirstPosition
+ getChildCount() - 1;
282 setSelectionInt(position
, shouldAnimate
);
286 public void setSelection(int position
) {
287 setNextSelectedPositionInt(position
);
294 * Makes the item at the supplied position selected.
296 * @param position Position to select
297 * @param animate Should the transition be animated
300 void setSelectionInt(int position
, boolean animate
) {
301 if (position
!= mOldSelectedPosition
) {
302 mBlockLayoutRequests
= true
;
303 int delta
= position
- mSelectedPosition
;
304 setNextSelectedPositionInt(position
);
305 layout(delta
, animate
);
306 mBlockLayoutRequests
= false
;
310 abstract void layout(int delta
, boolean animate
);
313 public View
getSelectedView() {
314 if (mItemCount
> 0 && mSelectedPosition
>= 0) {
315 return getChildAt(mSelectedPosition
- mFirstPosition
);
322 * Override to prevent spamming ourselves with layout requests
325 * @see android.view.View#requestLayout()
328 public void requestLayout() {
329 if (!mBlockLayoutRequests
) {
330 super.requestLayout();
335 public SpinnerAdapter
getAdapter() {
340 public int getCount() {
345 * Maps a point to a position in the list.
347 * @param x X in local coordinate
348 * @param y Y in local coordinate
349 * @return The position of the item which contains the specified point, or
350 * {@link #INVALID_POSITION} if the point does not intersect an item.
352 public int pointToPosition(int x
, int y
) {
353 Rect frame
= mTouchFrame
;
355 mTouchFrame
= new Rect();
359 final int count
= getChildCount();
360 for (int i
= count
- 1; i
>= 0; i
--) {
361 View child
= getChildAt(i
);
362 if (child
.getVisibility() == View
.VISIBLE
) {
363 child
.getHitRect(frame
);
364 if (frame
.contains(x
, y
)) {
365 return mFirstPosition
+ i
;
369 return INVALID_POSITION
;
372 static class SavedState
extends BaseSavedState
{
377 * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
379 SavedState(Parcelable superState
) {
384 * Constructor called from {@link #CREATOR}
386 private SavedState(Parcel
in) {
388 selectedId
= in.readLong();
389 position
= in.readInt();
393 public void writeToParcel(Parcel out
, int flags
) {
394 super.writeToParcel(out
, flags
);
395 out
.writeLong(selectedId
);
396 out
.writeInt(position
);
400 public String
toString() {
401 return "AbsSpinner.SavedState{"
402 + Integer
.toHexString(System
.identityHashCode(this))
403 + " selectedId=" + selectedId
404 + " position=" + position
+ "}";
407 public static final Parcelable
.Creator
<SavedState
> CREATOR
408 = new Parcelable
.Creator
<SavedState
>() {
409 public SavedState
createFromParcel(Parcel
in) {
410 return new SavedState(in);
413 public SavedState
[] newArray(int size
) {
414 return new SavedState
[size
];
420 public Parcelable
onSaveInstanceState() {
421 Parcelable superState
= super.onSaveInstanceState();
422 SavedState ss
= new SavedState(superState
);
423 ss
.selectedId
= getSelectedItemId();
424 if (ss
.selectedId
>= 0) {
425 ss
.position
= getSelectedItemPosition();
427 ss
.position
= INVALID_POSITION
;
433 public void onRestoreInstanceState(Parcelable state
) {
434 SavedState ss
= (SavedState
) state
;
436 super.onRestoreInstanceState(ss
.getSuperState());
438 if (ss
.selectedId
>= 0) {
441 mSyncRowId
= ss
.selectedId
;
442 mSyncPosition
= ss
.position
;
443 mSyncMode
= SYNC_SELECTED_POSITION
;
449 private final SparseArray
<View
> mScrapHeap
= new SparseArray
<View
>();
451 public void put(int position
, View v
) {
452 mScrapHeap
.put(position
, v
);
455 View
get(int position
) {
456 // System.out.print("Looking for " + position);
457 View result
= mScrapHeap
.get(position
);
458 if (result
!= null
) {
459 // System.out.println(" HIT");
460 mScrapHeap
.delete(position
);
462 // System.out.println(" MISS");
468 final SparseArray
<View
> scrapHeap
= mScrapHeap
;
469 final int count
= scrapHeap
.size();
470 for (int i
= 0; i
< count
; i
++) {
471 final View view
= scrapHeap
.valueAt(i
);
473 removeDetachedView(view
, true
);