ce0cb3bcaacc5d41cd5a59fe603eb6795bc781e3
[pub/Android/ownCloud.git] / actionbarsherlock / src / com / actionbarsherlock / internal / widget / IcsAbsSpinner.java
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 com.actionbarsherlock.internal.widget;
18
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;
30
31 /**
32 * An abstract base class for spinner widgets. SDK users will probably not
33 * need to use this class.
34 *
35 * @attr ref android.R.styleable#AbsSpinner_entries
36 */
37 public abstract class IcsAbsSpinner extends IcsAdapterView<SpinnerAdapter> {
38 private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
39
40 SpinnerAdapter mAdapter;
41
42 int mHeightMeasureSpec;
43 int mWidthMeasureSpec;
44 boolean mBlockLayoutRequests;
45
46 int mSelectionLeftPadding = 0;
47 int mSelectionTopPadding = 0;
48 int mSelectionRightPadding = 0;
49 int mSelectionBottomPadding = 0;
50 final Rect mSpinnerPadding = new Rect();
51
52 final RecycleBin mRecycler = new RecycleBin();
53 private DataSetObserver mDataSetObserver;
54
55 /** Temporary frame to hold a child View's frame rectangle */
56 private Rect mTouchFrame;
57
58 public IcsAbsSpinner(Context context) {
59 super(context);
60 initAbsSpinner();
61 }
62
63 public IcsAbsSpinner(Context context, AttributeSet attrs) {
64 this(context, attrs, 0);
65 }
66
67 public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
68 super(context, attrs, defStyle);
69 initAbsSpinner();
70
71 /*
72 TypedArray a = context.obtainStyledAttributes(attrs,
73 com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
74
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);
81 setAdapter(adapter);
82 }
83
84 a.recycle();
85 */
86 }
87
88 /**
89 * Common code for different constructor flavors
90 */
91 private void initAbsSpinner() {
92 setFocusable(true);
93 setWillNotDraw(false);
94 }
95
96 /**
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
101 */
102 @Override
103 public void setAdapter(SpinnerAdapter adapter) {
104 if (null != mAdapter) {
105 mAdapter.unregisterDataSetObserver(mDataSetObserver);
106 resetList();
107 }
108
109 mAdapter = adapter;
110
111 mOldSelectedPosition = INVALID_POSITION;
112 mOldSelectedRowId = INVALID_ROW_ID;
113
114 if (mAdapter != null) {
115 mOldItemCount = mItemCount;
116 mItemCount = mAdapter.getCount();
117 checkFocus();
118
119 mDataSetObserver = new AdapterDataSetObserver();
120 mAdapter.registerDataSetObserver(mDataSetObserver);
121
122 int position = mItemCount > 0 ? 0 : INVALID_POSITION;
123
124 setSelectedPositionInt(position);
125 setNextSelectedPositionInt(position);
126
127 if (mItemCount == 0) {
128 // Nothing selected
129 checkSelectionChanged();
130 }
131
132 } else {
133 checkFocus();
134 resetList();
135 // Nothing selected
136 checkSelectionChanged();
137 }
138
139 requestLayout();
140 }
141
142 /**
143 * Clear out all children from the list
144 */
145 void resetList() {
146 mDataChanged = false;
147 mNeedSync = false;
148
149 removeAllViewsInLayout();
150 mOldSelectedPosition = INVALID_POSITION;
151 mOldSelectedRowId = INVALID_ROW_ID;
152
153 setSelectedPositionInt(INVALID_POSITION);
154 setNextSelectedPositionInt(INVALID_POSITION);
155 invalidate();
156 }
157
158 /**
159 * @see android.view.View#measure(int, int)
160 *
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
164 * plus padding.
165 */
166 @Override
167 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
168 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
169 int widthSize;
170 int heightSize;
171
172 final int mPaddingLeft = getPaddingLeft();
173 final int mPaddingTop = getPaddingTop();
174 final int mPaddingRight = getPaddingRight();
175 final int mPaddingBottom = getPaddingBottom();
176
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;
185
186 if (mDataChanged) {
187 handleDataChanged();
188 }
189
190 int preferredHeight = 0;
191 int preferredWidth = 0;
192 boolean needsMeasuring = true;
193
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);
198 if (view == null) {
199 // Make a new one
200 view = mAdapter.getView(selectedPosition, null, this);
201 }
202
203 if (view != null) {
204 // Put in recycler for re-measuring and/or layout
205 mRecycler.put(selectedPosition, view);
206 }
207
208 if (view != null) {
209 if (view.getLayoutParams() == null) {
210 mBlockLayoutRequests = true;
211 view.setLayoutParams(generateDefaultLayoutParams());
212 mBlockLayoutRequests = false;
213 }
214 measureChild(view, widthMeasureSpec, heightMeasureSpec);
215
216 preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
217 preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
218
219 needsMeasuring = false;
220 }
221 }
222
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;
228 }
229 }
230
231 preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
232 preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
233
234 if (IS_HONEYCOMB) {
235 heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
236 widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
237 } else {
238 heightSize = resolveSize(preferredHeight, heightMeasureSpec);
239 widthSize = resolveSize(preferredWidth, widthMeasureSpec);
240 }
241
242 setMeasuredDimension(widthSize, heightSize);
243 mHeightMeasureSpec = heightMeasureSpec;
244 mWidthMeasureSpec = widthMeasureSpec;
245 }
246
247 int getChildHeight(View child) {
248 return child.getMeasuredHeight();
249 }
250
251 int getChildWidth(View child) {
252 return child.getMeasuredWidth();
253 }
254
255 @Override
256 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
257 return new ViewGroup.LayoutParams(
258 ViewGroup.LayoutParams.MATCH_PARENT,
259 ViewGroup.LayoutParams.WRAP_CONTENT);
260 }
261
262 void recycleAllViews() {
263 final int childCount = getChildCount();
264 final IcsAbsSpinner.RecycleBin recycleBin = mRecycler;
265 final int position = mFirstPosition;
266
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);
272 }
273 }
274
275 /**
276 * Jump directly to a specific item in the adapter data.
277 */
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);
283 }
284
285 @Override
286 public void setSelection(int position) {
287 setNextSelectedPositionInt(position);
288 requestLayout();
289 invalidate();
290 }
291
292
293 /**
294 * Makes the item at the supplied position selected.
295 *
296 * @param position Position to select
297 * @param animate Should the transition be animated
298 *
299 */
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;
307 }
308 }
309
310 abstract void layout(int delta, boolean animate);
311
312 @Override
313 public View getSelectedView() {
314 if (mItemCount > 0 && mSelectedPosition >= 0) {
315 return getChildAt(mSelectedPosition - mFirstPosition);
316 } else {
317 return null;
318 }
319 }
320
321 /**
322 * Override to prevent spamming ourselves with layout requests
323 * as we place views
324 *
325 * @see android.view.View#requestLayout()
326 */
327 @Override
328 public void requestLayout() {
329 if (!mBlockLayoutRequests) {
330 super.requestLayout();
331 }
332 }
333
334 @Override
335 public SpinnerAdapter getAdapter() {
336 return mAdapter;
337 }
338
339 @Override
340 public int getCount() {
341 return mItemCount;
342 }
343
344 /**
345 * Maps a point to a position in the list.
346 *
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.
351 */
352 public int pointToPosition(int x, int y) {
353 Rect frame = mTouchFrame;
354 if (frame == null) {
355 mTouchFrame = new Rect();
356 frame = mTouchFrame;
357 }
358
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;
366 }
367 }
368 }
369 return INVALID_POSITION;
370 }
371
372 static class SavedState extends BaseSavedState {
373 long selectedId;
374 int position;
375
376 /**
377 * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
378 */
379 SavedState(Parcelable superState) {
380 super(superState);
381 }
382
383 /**
384 * Constructor called from {@link #CREATOR}
385 */
386 private SavedState(Parcel in) {
387 super(in);
388 selectedId = in.readLong();
389 position = in.readInt();
390 }
391
392 @Override
393 public void writeToParcel(Parcel out, int flags) {
394 super.writeToParcel(out, flags);
395 out.writeLong(selectedId);
396 out.writeInt(position);
397 }
398
399 @Override
400 public String toString() {
401 return "AbsSpinner.SavedState{"
402 + Integer.toHexString(System.identityHashCode(this))
403 + " selectedId=" + selectedId
404 + " position=" + position + "}";
405 }
406
407 public static final Parcelable.Creator<SavedState> CREATOR
408 = new Parcelable.Creator<SavedState>() {
409 public SavedState createFromParcel(Parcel in) {
410 return new SavedState(in);
411 }
412
413 public SavedState[] newArray(int size) {
414 return new SavedState[size];
415 }
416 };
417 }
418
419 @Override
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();
426 } else {
427 ss.position = INVALID_POSITION;
428 }
429 return ss;
430 }
431
432 @Override
433 public void onRestoreInstanceState(Parcelable state) {
434 SavedState ss = (SavedState) state;
435
436 super.onRestoreInstanceState(ss.getSuperState());
437
438 if (ss.selectedId >= 0) {
439 mDataChanged = true;
440 mNeedSync = true;
441 mSyncRowId = ss.selectedId;
442 mSyncPosition = ss.position;
443 mSyncMode = SYNC_SELECTED_POSITION;
444 requestLayout();
445 }
446 }
447
448 class RecycleBin {
449 private final SparseArray<View> mScrapHeap = new SparseArray<View>();
450
451 public void put(int position, View v) {
452 mScrapHeap.put(position, v);
453 }
454
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);
461 } else {
462 // System.out.println(" MISS");
463 }
464 return result;
465 }
466
467 void clear() {
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);
472 if (view != null) {
473 removeDetachedView(view, true);
474 }
475 }
476 scrapHeap.clear();
477 }
478 }
479 }