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);