blob: 6a4ad75a47a49ea1f6e3f04b2d7a9e324fdd8b7c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.widget;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.database.DataSetObserver;
24import android.graphics.Rect;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.util.AttributeSet;
28import android.util.SparseArray;
29import android.view.View;
30import android.view.ViewGroup;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080031import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
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 */
40public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 SpinnerAdapter mAdapter;
42
43 int mHeightMeasureSpec;
44 int mWidthMeasureSpec;
Romain Guyafc01552010-02-22 16:48:47 -080045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 int mSelectionLeftPadding = 0;
47 int mSelectionTopPadding = 0;
48 int mSelectionRightPadding = 0;
49 int mSelectionBottomPadding = 0;
Romain Guyafc01552010-02-22 16:48:47 -080050 final Rect mSpinnerPadding = new Rect();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
Romain Guyafc01552010-02-22 16:48:47 -080052 final RecycleBin mRecycler = new RecycleBin();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 private DataSetObserver mDataSetObserver;
54
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 /** Temporary frame to hold a child View's frame rectangle */
56 private Rect mTouchFrame;
57
58 public AbsSpinner(Context context) {
59 super(context);
60 initAbsSpinner();
61 }
62
63 public AbsSpinner(Context context, AttributeSet attrs) {
64 this(context, attrs, 0);
65 }
66
Alan Viverette617feb92013-09-09 18:09:13 -070067 public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
68 this(context, attrs, defStyleAttr, 0);
69 }
70
71 public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
72 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 initAbsSpinner();
74
Alan Viverette617feb92013-09-09 18:09:13 -070075 final TypedArray a = context.obtainStyledAttributes(
76 attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077
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 */
93 private void initAbsSpinner() {
94 setFocusable(true);
95 setWillNotDraw(false);
96 }
97
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 /**
99 * The Adapter is used to provide the data which backs this Spinner.
100 * It also provides methods to transform spinner items based on their position
101 * relative to the selected item.
102 * @param adapter The SpinnerAdapter to use for this Spinner
103 */
104 @Override
105 public void setAdapter(SpinnerAdapter adapter) {
106 if (null != mAdapter) {
107 mAdapter.unregisterDataSetObserver(mDataSetObserver);
108 resetList();
109 }
110
111 mAdapter = adapter;
112
113 mOldSelectedPosition = INVALID_POSITION;
114 mOldSelectedRowId = INVALID_ROW_ID;
115
116 if (mAdapter != null) {
117 mOldItemCount = mItemCount;
118 mItemCount = mAdapter.getCount();
119 checkFocus();
120
121 mDataSetObserver = new AdapterDataSetObserver();
122 mAdapter.registerDataSetObserver(mDataSetObserver);
123
124 int position = mItemCount > 0 ? 0 : INVALID_POSITION;
125
126 setSelectedPositionInt(position);
127 setNextSelectedPositionInt(position);
128
129 if (mItemCount == 0) {
130 // Nothing selected
131 checkSelectionChanged();
132 }
133
134 } else {
135 checkFocus();
136 resetList();
137 // Nothing selected
138 checkSelectionChanged();
139 }
140
141 requestLayout();
142 }
143
144 /**
145 * Clear out all children from the list
146 */
147 void resetList() {
148 mDataChanged = false;
149 mNeedSync = false;
150
151 removeAllViewsInLayout();
152 mOldSelectedPosition = INVALID_POSITION;
153 mOldSelectedRowId = INVALID_ROW_ID;
154
155 setSelectedPositionInt(INVALID_POSITION);
156 setNextSelectedPositionInt(INVALID_POSITION);
157 invalidate();
158 }
159
160 /**
161 * @see android.view.View#measure(int, int)
162 *
163 * Figure out the dimensions of this Spinner. The width comes from
164 * the widthMeasureSpec as Spinnners can't have their width set to
165 * UNSPECIFIED. The height is based on the height of the selected item
166 * plus padding.
167 */
168 @Override
169 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
170 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
171 int widthSize;
172 int heightSize;
173
174 mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft
175 : mSelectionLeftPadding;
176 mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop
177 : mSelectionTopPadding;
178 mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight
179 : mSelectionRightPadding;
180 mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom
181 : mSelectionBottomPadding;
182
183 if (mDataChanged) {
184 handleDataChanged();
185 }
186
187 int preferredHeight = 0;
188 int preferredWidth = 0;
189 boolean needsMeasuring = true;
190
191 int selectedPosition = getSelectedItemPosition();
Romain Guyafc01552010-02-22 16:48:47 -0800192 if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 // Try looking in the recycler. (Maybe we were measured once already)
194 View view = mRecycler.get(selectedPosition);
195 if (view == null) {
196 // Make a new one
197 view = mAdapter.getView(selectedPosition, null, this);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700198
199 if (view.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
200 view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
201 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 }
203
204 if (view != null) {
205 // Put in recycler for re-measuring and/or layout
206 mRecycler.put(selectedPosition, view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 if (view.getLayoutParams() == null) {
209 mBlockLayoutRequests = true;
210 view.setLayoutParams(generateDefaultLayoutParams());
211 mBlockLayoutRequests = false;
212 }
213 measureChild(view, widthMeasureSpec, heightMeasureSpec);
214
215 preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
216 preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
217
218 needsMeasuring = false;
219 }
220 }
221
222 if (needsMeasuring) {
223 // No views -- just use padding
224 preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
225 if (widthMode == MeasureSpec.UNSPECIFIED) {
226 preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
227 }
228 }
229
230 preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
231 preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
232
Dianne Hackborn189ee182010-12-02 21:48:53 -0800233 heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
234 widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235
236 setMeasuredDimension(widthSize, heightSize);
237 mHeightMeasureSpec = heightMeasureSpec;
238 mWidthMeasureSpec = widthMeasureSpec;
239 }
240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 int getChildHeight(View child) {
242 return child.getMeasuredHeight();
243 }
244
245 int getChildWidth(View child) {
246 return child.getMeasuredWidth();
247 }
248
249 @Override
250 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
251 return new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -0800252 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 ViewGroup.LayoutParams.WRAP_CONTENT);
254 }
255
256 void recycleAllViews() {
Romain Guyafc01552010-02-22 16:48:47 -0800257 final int childCount = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 final AbsSpinner.RecycleBin recycleBin = mRecycler;
Romain Guyafc01552010-02-22 16:48:47 -0800259 final int position = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260
261 // All views go in recycler
Romain Guyafc01552010-02-22 16:48:47 -0800262 for (int i = 0; i < childCount; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 View v = getChildAt(i);
Romain Guyafc01552010-02-22 16:48:47 -0800264 int index = position + i;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 recycleBin.put(index, v);
266 }
267 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268
269 /**
270 * Jump directly to a specific item in the adapter data.
271 */
272 public void setSelection(int position, boolean animate) {
273 // Animate only if requested position is already on screen somewhere
274 boolean shouldAnimate = animate && mFirstPosition <= position &&
275 position <= mFirstPosition + getChildCount() - 1;
276 setSelectionInt(position, shouldAnimate);
277 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278
279 @Override
280 public void setSelection(int position) {
281 setNextSelectedPositionInt(position);
282 requestLayout();
283 invalidate();
284 }
285
286
287 /**
288 * Makes the item at the supplied position selected.
289 *
290 * @param position Position to select
291 * @param animate Should the transition be animated
292 *
293 */
294 void setSelectionInt(int position, boolean animate) {
295 if (position != mOldSelectedPosition) {
296 mBlockLayoutRequests = true;
297 int delta = position - mSelectedPosition;
298 setNextSelectedPositionInt(position);
299 layout(delta, animate);
300 mBlockLayoutRequests = false;
301 }
302 }
303
304 abstract void layout(int delta, boolean animate);
305
306 @Override
307 public View getSelectedView() {
308 if (mItemCount > 0 && mSelectedPosition >= 0) {
309 return getChildAt(mSelectedPosition - mFirstPosition);
310 } else {
311 return null;
312 }
313 }
314
315 /**
316 * Override to prevent spamming ourselves with layout requests
317 * as we place views
318 *
319 * @see android.view.View#requestLayout()
320 */
321 @Override
322 public void requestLayout() {
323 if (!mBlockLayoutRequests) {
324 super.requestLayout();
325 }
326 }
327
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 @Override
329 public SpinnerAdapter getAdapter() {
330 return mAdapter;
331 }
332
333 @Override
334 public int getCount() {
335 return mItemCount;
336 }
337
338 /**
339 * Maps a point to a position in the list.
340 *
341 * @param x X in local coordinate
342 * @param y Y in local coordinate
343 * @return The position of the item which contains the specified point, or
344 * {@link #INVALID_POSITION} if the point does not intersect an item.
345 */
346 public int pointToPosition(int x, int y) {
347 Rect frame = mTouchFrame;
348 if (frame == null) {
349 mTouchFrame = new Rect();
350 frame = mTouchFrame;
351 }
352
353 final int count = getChildCount();
354 for (int i = count - 1; i >= 0; i--) {
355 View child = getChildAt(i);
356 if (child.getVisibility() == View.VISIBLE) {
357 child.getHitRect(frame);
358 if (frame.contains(x, y)) {
359 return mFirstPosition + i;
360 }
361 }
362 }
363 return INVALID_POSITION;
364 }
365
366 static class SavedState extends BaseSavedState {
367 long selectedId;
368 int position;
369
370 /**
371 * Constructor called from {@link AbsSpinner#onSaveInstanceState()}
372 */
373 SavedState(Parcelable superState) {
374 super(superState);
375 }
376
377 /**
378 * Constructor called from {@link #CREATOR}
379 */
Adam Powell235ae5f2012-12-10 13:38:03 -0800380 SavedState(Parcel in) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 super(in);
382 selectedId = in.readLong();
383 position = in.readInt();
384 }
385
386 @Override
387 public void writeToParcel(Parcel out, int flags) {
388 super.writeToParcel(out, flags);
389 out.writeLong(selectedId);
390 out.writeInt(position);
391 }
392
393 @Override
394 public String toString() {
395 return "AbsSpinner.SavedState{"
396 + Integer.toHexString(System.identityHashCode(this))
397 + " selectedId=" + selectedId
398 + " position=" + position + "}";
399 }
400
401 public static final Parcelable.Creator<SavedState> CREATOR
402 = new Parcelable.Creator<SavedState>() {
403 public SavedState createFromParcel(Parcel in) {
404 return new SavedState(in);
405 }
406
407 public SavedState[] newArray(int size) {
408 return new SavedState[size];
409 }
410 };
411 }
412
413 @Override
414 public Parcelable onSaveInstanceState() {
415 Parcelable superState = super.onSaveInstanceState();
416 SavedState ss = new SavedState(superState);
417 ss.selectedId = getSelectedItemId();
418 if (ss.selectedId >= 0) {
419 ss.position = getSelectedItemPosition();
420 } else {
421 ss.position = INVALID_POSITION;
422 }
423 return ss;
424 }
425
426 @Override
427 public void onRestoreInstanceState(Parcelable state) {
428 SavedState ss = (SavedState) state;
429
430 super.onRestoreInstanceState(ss.getSuperState());
431
432 if (ss.selectedId >= 0) {
433 mDataChanged = true;
434 mNeedSync = true;
435 mSyncRowId = ss.selectedId;
436 mSyncPosition = ss.position;
437 mSyncMode = SYNC_SELECTED_POSITION;
438 requestLayout();
439 }
440 }
441
442 class RecycleBin {
Romain Guyafc01552010-02-22 16:48:47 -0800443 private final SparseArray<View> mScrapHeap = new SparseArray<View>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444
445 public void put(int position, View v) {
446 mScrapHeap.put(position, v);
447 }
448
449 View get(int position) {
450 // System.out.print("Looking for " + position);
451 View result = mScrapHeap.get(position);
452 if (result != null) {
453 // System.out.println(" HIT");
454 mScrapHeap.delete(position);
455 } else {
456 // System.out.println(" MISS");
457 }
458 return result;
459 }
Romain Guyafc01552010-02-22 16:48:47 -0800460
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 void clear() {
462 final SparseArray<View> scrapHeap = mScrapHeap;
463 final int count = scrapHeap.size();
464 for (int i = 0; i < count; i++) {
465 final View view = scrapHeap.valueAt(i);
466 if (view != null) {
467 removeDetachedView(view, true);
468 }
469 }
470 scrapHeap.clear();
471 }
472 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800473
474 @Override
475 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
476 super.onInitializeAccessibilityEvent(event);
477 event.setClassName(AbsSpinner.class.getName());
478 }
479
480 @Override
481 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
482 super.onInitializeAccessibilityNodeInfo(info);
483 info.setClassName(AbsSpinner.class.getName());
484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485}