Created
February 22, 2012 05:38
-
-
Save JakeWharton/1881727 to your computer and use it in GitHub Desktop.
Beginnings of a pre-HC dropdown Spinner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2006 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.actionbarsherlock.internal.widget; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.database.DataSetObserver; | |
import android.graphics.Rect; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.util.AttributeSet; | |
import android.util.SparseArray; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.AdapterView; | |
import android.widget.SpinnerAdapter; | |
/** | |
* An abstract base class for spinner widgets. SDK users will probably not | |
* need to use this class. | |
* | |
* @attr ref android.R.styleable#AbsSpinner_entries | |
*/ | |
public abstract class IcsAbsSpinner extends AdapterView<SpinnerAdapter> { | |
SpinnerAdapter mAdapter; | |
int mHeightMeasureSpec; | |
int mWidthMeasureSpec; | |
boolean mBlockLayoutRequests; | |
int mSelectionLeftPadding = 0; | |
int mSelectionTopPadding = 0; | |
int mSelectionRightPadding = 0; | |
int mSelectionBottomPadding = 0; | |
final Rect mSpinnerPadding = new Rect(); | |
final RecycleBin mRecycler = new RecycleBin(); | |
private DataSetObserver mDataSetObserver; | |
/** Temporary frame to hold a child View's frame rectangle */ | |
private Rect mTouchFrame; | |
public IcsAbsSpinner(Context context) { | |
super(context); | |
initAbsSpinner(); | |
} | |
public IcsAbsSpinner(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public IcsAbsSpinner(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
initAbsSpinner(); | |
TypedArray a = context.obtainStyledAttributes(attrs, | |
com.android.internal.R.styleable.AbsSpinner, defStyle, 0); | |
CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries); | |
if (entries != null) { | |
ArrayAdapter<CharSequence> adapter = | |
new ArrayAdapter<CharSequence>(context, | |
R.layout.simple_spinner_item, entries); | |
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); | |
setAdapter(adapter); | |
} | |
a.recycle(); | |
} | |
/** | |
* Common code for different constructor flavors | |
*/ | |
private void initAbsSpinner() { | |
setFocusable(true); | |
setWillNotDraw(false); | |
} | |
/** | |
* The Adapter is used to provide the data which backs this Spinner. | |
* It also provides methods to transform spinner items based on their position | |
* relative to the selected item. | |
* @param adapter The SpinnerAdapter to use for this Spinner | |
*/ | |
@Override | |
public void setAdapter(SpinnerAdapter adapter) { | |
if (null != mAdapter) { | |
mAdapter.unregisterDataSetObserver(mDataSetObserver); | |
resetList(); | |
} | |
mAdapter = adapter; | |
mOldSelectedPosition = INVALID_POSITION; | |
mOldSelectedRowId = INVALID_ROW_ID; | |
if (mAdapter != null) { | |
mOldItemCount = mItemCount; | |
mItemCount = mAdapter.getCount(); | |
checkFocus(); | |
mDataSetObserver = new AdapterDataSetObserver(); | |
mAdapter.registerDataSetObserver(mDataSetObserver); | |
int position = mItemCount > 0 ? 0 : INVALID_POSITION; | |
setSelectedPositionInt(position); | |
setNextSelectedPositionInt(position); | |
if (mItemCount == 0) { | |
// Nothing selected | |
checkSelectionChanged(); | |
} | |
} else { | |
checkFocus(); | |
resetList(); | |
// Nothing selected | |
checkSelectionChanged(); | |
} | |
requestLayout(); | |
} | |
/** | |
* Clear out all children from the list | |
*/ | |
void resetList() { | |
mDataChanged = false; | |
mNeedSync = false; | |
removeAllViewsInLayout(); | |
mOldSelectedPosition = INVALID_POSITION; | |
mOldSelectedRowId = INVALID_ROW_ID; | |
setSelectedPositionInt(INVALID_POSITION); | |
setNextSelectedPositionInt(INVALID_POSITION); | |
invalidate(); | |
} | |
/** | |
* @see android.view.View#measure(int, int) | |
* | |
* Figure out the dimensions of this Spinner. The width comes from | |
* the widthMeasureSpec as Spinnners can't have their width set to | |
* UNSPECIFIED. The height is based on the height of the selected item | |
* plus padding. | |
*/ | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
int widthMode = MeasureSpec.getMode(widthMeasureSpec); | |
int widthSize; | |
int heightSize; | |
int mPaddingLeft = getPaddingLeft(); | |
int mPaddingTop = getPaddingTop(); | |
int mPaddingRight = getPaddingRight(); | |
int mPaddingBottom = getPaddingBottom(); | |
mSpinnerPadding.left = mPaddingLeft > mSelectionLeftPadding ? mPaddingLeft | |
: mSelectionLeftPadding; | |
mSpinnerPadding.top = mPaddingTop > mSelectionTopPadding ? mPaddingTop | |
: mSelectionTopPadding; | |
mSpinnerPadding.right = mPaddingRight > mSelectionRightPadding ? mPaddingRight | |
: mSelectionRightPadding; | |
mSpinnerPadding.bottom = mPaddingBottom > mSelectionBottomPadding ? mPaddingBottom | |
: mSelectionBottomPadding; | |
if (mDataChanged) { | |
handleDataChanged(); | |
} | |
int preferredHeight = 0; | |
int preferredWidth = 0; | |
boolean needsMeasuring = true; | |
int selectedPosition = getSelectedItemPosition(); | |
if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) { | |
// Try looking in the recycler. (Maybe we were measured once already) | |
View view = mRecycler.get(selectedPosition); | |
if (view == null) { | |
// Make a new one | |
view = mAdapter.getView(selectedPosition, null, this); | |
} | |
if (view != null) { | |
// Put in recycler for re-measuring and/or layout | |
mRecycler.put(selectedPosition, view); | |
} | |
if (view != null) { | |
if (view.getLayoutParams() == null) { | |
mBlockLayoutRequests = true; | |
view.setLayoutParams(generateDefaultLayoutParams()); | |
mBlockLayoutRequests = false; | |
} | |
measureChild(view, widthMeasureSpec, heightMeasureSpec); | |
preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom; | |
preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right; | |
needsMeasuring = false; | |
} | |
} | |
if (needsMeasuring) { | |
// No views -- just use padding | |
preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom; | |
if (widthMode == MeasureSpec.UNSPECIFIED) { | |
preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right; | |
} | |
} | |
preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight()); | |
preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth()); | |
heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0); | |
widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0); | |
setMeasuredDimension(widthSize, heightSize); | |
mHeightMeasureSpec = heightMeasureSpec; | |
mWidthMeasureSpec = widthMeasureSpec; | |
} | |
int getChildHeight(View child) { | |
return child.getMeasuredHeight(); | |
} | |
int getChildWidth(View child) { | |
return child.getMeasuredWidth(); | |
} | |
@Override | |
protected ViewGroup.LayoutParams generateDefaultLayoutParams() { | |
return new ViewGroup.LayoutParams( | |
ViewGroup.LayoutParams.MATCH_PARENT, | |
ViewGroup.LayoutParams.WRAP_CONTENT); | |
} | |
void recycleAllViews() { | |
final int childCount = getChildCount(); | |
final IcsAbsSpinner.RecycleBin recycleBin = mRecycler; | |
final int position = mFirstPosition; | |
// All views go in recycler | |
for (int i = 0; i < childCount; i++) { | |
View v = getChildAt(i); | |
int index = position + i; | |
recycleBin.put(index, v); | |
} | |
} | |
/** | |
* Jump directly to a specific item in the adapter data. | |
*/ | |
public void setSelection(int position, boolean animate) { | |
// Animate only if requested position is already on screen somewhere | |
boolean shouldAnimate = animate && mFirstPosition <= position && | |
position <= mFirstPosition + getChildCount() - 1; | |
setSelectionInt(position, shouldAnimate); | |
} | |
@Override | |
public void setSelection(int position) { | |
setNextSelectedPositionInt(position); | |
requestLayout(); | |
invalidate(); | |
} | |
/** | |
* Makes the item at the supplied position selected. | |
* | |
* @param position Position to select | |
* @param animate Should the transition be animated | |
* | |
*/ | |
void setSelectionInt(int position, boolean animate) { | |
if (position != mOldSelectedPosition) { | |
mBlockLayoutRequests = true; | |
int delta = position - mSelectedPosition; | |
setNextSelectedPositionInt(position); | |
layout(delta, animate); | |
mBlockLayoutRequests = false; | |
} | |
} | |
abstract void layout(int delta, boolean animate); | |
@Override | |
public View getSelectedView() { | |
int mSelectedPosition = getSelectedItemPosition(); | |
int mItemCount = getAdapter().getCount(); | |
if (mItemCount > 0 && mSelectedPosition >= 0) { | |
return getChildAt(mSelectedPosition - mFirstPosition); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Override to prevent spamming ourselves with layout requests | |
* as we place views | |
* | |
* @see android.view.View#requestLayout() | |
*/ | |
@Override | |
public void requestLayout() { | |
if (!mBlockLayoutRequests) { | |
super.requestLayout(); | |
} | |
} | |
@Override | |
public SpinnerAdapter getAdapter() { | |
return mAdapter; | |
} | |
@Override | |
public int getCount() { | |
return getAdapter().getCount(); | |
} | |
/** | |
* Maps a point to a position in the list. | |
* | |
* @param x X in local coordinate | |
* @param y Y in local coordinate | |
* @return The position of the item which contains the specified point, or | |
* {@link #INVALID_POSITION} if the point does not intersect an item. | |
*/ | |
public int pointToPosition(int x, int y) { | |
Rect frame = mTouchFrame; | |
if (frame == null) { | |
mTouchFrame = new Rect(); | |
frame = mTouchFrame; | |
} | |
final int count = getChildCount(); | |
for (int i = count - 1; i >= 0; i--) { | |
View child = getChildAt(i); | |
if (child.getVisibility() == View.VISIBLE) { | |
child.getHitRect(frame); | |
if (frame.contains(x, y)) { | |
return mFirstPosition + i; | |
} | |
} | |
} | |
return INVALID_POSITION; | |
} | |
static class SavedState extends BaseSavedState { | |
long selectedId; | |
int position; | |
/** | |
* Constructor called from {@link AbsSpinner#onSaveInstanceState()} | |
*/ | |
SavedState(Parcelable superState) { | |
super(superState); | |
} | |
/** | |
* Constructor called from {@link #CREATOR} | |
*/ | |
private SavedState(Parcel in) { | |
super(in); | |
selectedId = in.readLong(); | |
position = in.readInt(); | |
} | |
@Override | |
public void writeToParcel(Parcel out, int flags) { | |
super.writeToParcel(out, flags); | |
out.writeLong(selectedId); | |
out.writeInt(position); | |
} | |
@Override | |
public String toString() { | |
return "AbsSpinner.SavedState{" | |
+ Integer.toHexString(System.identityHashCode(this)) | |
+ " selectedId=" + selectedId | |
+ " position=" + position + "}"; | |
} | |
public static final Parcelable.Creator<SavedState> CREATOR | |
= new Parcelable.Creator<SavedState>() { | |
public SavedState createFromParcel(Parcel in) { | |
return new SavedState(in); | |
} | |
public SavedState[] newArray(int size) { | |
return new SavedState[size]; | |
} | |
}; | |
} | |
@Override | |
public Parcelable onSaveInstanceState() { | |
Parcelable superState = super.onSaveInstanceState(); | |
SavedState ss = new SavedState(superState); | |
ss.selectedId = getSelectedItemId(); | |
if (ss.selectedId >= 0) { | |
ss.position = getSelectedItemPosition(); | |
} else { | |
ss.position = INVALID_POSITION; | |
} | |
return ss; | |
} | |
@Override | |
public void onRestoreInstanceState(Parcelable state) { | |
SavedState ss = (SavedState) state; | |
super.onRestoreInstanceState(ss.getSuperState()); | |
if (ss.selectedId >= 0) { | |
mDataChanged = true; | |
mNeedSync = true; | |
mSyncRowId = ss.selectedId; | |
mSyncPosition = ss.position; | |
mSyncMode = SYNC_SELECTED_POSITION; | |
requestLayout(); | |
} | |
} | |
class RecycleBin { | |
private final SparseArray<View> mScrapHeap = new SparseArray<View>(); | |
public void put(int position, View v) { | |
mScrapHeap.put(position, v); | |
} | |
View get(int position) { | |
// System.out.print("Looking for " + position); | |
View result = mScrapHeap.get(position); | |
if (result != null) { | |
// System.out.println(" HIT"); | |
mScrapHeap.delete(position); | |
} else { | |
// System.out.println(" MISS"); | |
} | |
return result; | |
} | |
void clear() { | |
final SparseArray<View> scrapHeap = mScrapHeap; | |
final int count = scrapHeap.size(); | |
for (int i = 0; i < count; i++) { | |
final View view = scrapHeap.valueAt(i); | |
if (view != null) { | |
removeDetachedView(view, true); | |
} | |
} | |
scrapHeap.clear(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2007 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.actionbarsherlock.internal.widget; | |
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; | |
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; | |
import android.app.AlertDialog; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.DialogInterface.OnClickListener; | |
import android.content.res.TypedArray; | |
import android.database.DataSetObserver; | |
import android.graphics.Rect; | |
import android.graphics.drawable.Drawable; | |
import android.util.AttributeSet; | |
import android.view.Gravity; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.AdapterView; | |
import android.widget.ListAdapter; | |
import android.widget.ListPopupWindow; | |
import android.widget.ListView; | |
import android.widget.SpinnerAdapter; | |
/** | |
* A view that displays one child at a time and lets the user pick among them. | |
* The items in the Spinner come from the {@link Adapter} associated with | |
* this view. | |
* | |
* <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner | |
* tutorial</a>.</p> | |
* | |
* @attr ref android.R.styleable#Spinner_prompt | |
*/ | |
public class IcsSpinner extends IcsAbsSpinner implements OnClickListener { | |
// Only measure this many items to get a decent max width. | |
private static final int MAX_ITEMS_MEASURED = 15; | |
/** | |
* Use a dropdown anchored to the Spinner for selecting spinner options. | |
*/ | |
public static final int MODE_DROPDOWN = 1; | |
private SpinnerPopup mPopup; | |
private DropDownAdapter mTempAdapter; | |
int mDropDownWidth; | |
private int mGravity; | |
private boolean mDisableChildrenWhenDisabled; | |
private Rect mTempRect = new Rect(); | |
/** | |
* Construct a new spinner with the given context's theme, the supplied attribute set, | |
* and default style. | |
* | |
* @param context The Context the view is running in, through which it can | |
* access the current theme, resources, etc. | |
* @param attrs The attributes of the XML tag that is inflating the view. | |
* @param defStyle The default style to apply to this view. If 0, no style | |
* will be applied (beyond what is included in the theme). This may | |
* either be an attribute resource, whose value will be retrieved | |
* from the current theme, or an explicit style resource. | |
*/ | |
public IcsSpinner(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
TypedArray a = context.obtainStyledAttributes(attrs, | |
com.android.internal.R.styleable.Spinner, defStyle, 0); | |
DropdownPopup popup = new DropdownPopup(context, attrs, defStyle); | |
mDropDownWidth = a.getLayoutDimension( | |
com.android.internal.R.styleable.Spinner_dropDownWidth, | |
ViewGroup.LayoutParams.WRAP_CONTENT); | |
popup.setBackgroundDrawable(a.getDrawable( | |
com.android.internal.R.styleable.Spinner_popupBackground)); | |
final int verticalOffset = a.getDimensionPixelOffset( | |
com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0); | |
if (verticalOffset != 0) { | |
popup.setVerticalOffset(verticalOffset); | |
} | |
final int horizontalOffset = a.getDimensionPixelOffset( | |
com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0); | |
if (horizontalOffset != 0) { | |
popup.setHorizontalOffset(horizontalOffset); | |
} | |
mPopup = popup; | |
mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER); | |
mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt)); | |
mDisableChildrenWhenDisabled = a.getBoolean( | |
com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false); | |
a.recycle(); | |
// Base constructor can call setAdapter before we initialize mPopup. | |
// Finish setting things up if this happened. | |
if (mTempAdapter != null) { | |
mPopup.setAdapter(mTempAdapter); | |
mTempAdapter = null; | |
} | |
} | |
@Override | |
public void setEnabled(boolean enabled) { | |
super.setEnabled(enabled); | |
if (mDisableChildrenWhenDisabled) { | |
final int count = getChildCount(); | |
for (int i = 0; i < count; i++) { | |
getChildAt(i).setEnabled(enabled); | |
} | |
} | |
} | |
/** | |
* Describes how the selected item view is positioned. Currently only the horizontal component | |
* is used. The default is determined by the current theme. | |
* | |
* @param gravity See {@link android.view.Gravity} | |
* | |
* @attr ref android.R.styleable#Spinner_gravity | |
*/ | |
public void setGravity(int gravity) { | |
if (mGravity != gravity) { | |
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { | |
gravity |= Gravity.LEFT; | |
} | |
mGravity = gravity; | |
requestLayout(); | |
} | |
} | |
@Override | |
public void setAdapter(SpinnerAdapter adapter) { | |
super.setAdapter(adapter); | |
if (mPopup != null) { | |
mPopup.setAdapter(new DropDownAdapter(adapter)); | |
} else { | |
mTempAdapter = new DropDownAdapter(adapter); | |
} | |
} | |
@Override | |
public int getBaseline() { | |
View child = null; | |
SpinnerAdapter adapter = getAdapter(); | |
if (getChildCount() > 0) { | |
child = getChildAt(0); | |
} else if (adapter != null && adapter.getCount() > 0) { | |
child = makeAndAddView(0); | |
//XXX mRecycler.put(0, child); | |
removeAllViewsInLayout(); | |
} | |
if (child != null) { | |
final int childBaseline = child.getBaseline(); | |
return childBaseline >= 0 ? child.getTop() + childBaseline : -1; | |
} else { | |
return -1; | |
} | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
super.onDetachedFromWindow(); | |
if (mPopup != null && mPopup.isShowing()) { | |
mPopup.dismiss(); | |
} | |
} | |
/** | |
* <p>A spinner does not support item click events. Calling this method | |
* will raise an exception.</p> | |
* | |
* @param l this listener will be ignored | |
*/ | |
@Override | |
public void setOnItemClickListener(OnItemClickListener l) { | |
throw new RuntimeException("setOnItemClickListener cannot be used with a spinner."); | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { | |
final int measuredWidth = getMeasuredWidth(); | |
setMeasuredDimension(Math.min(Math.max(measuredWidth, | |
measureContentWidth(getAdapter(), getBackground())), | |
MeasureSpec.getSize(widthMeasureSpec)), | |
getMeasuredHeight()); | |
} | |
} | |
/** | |
* @see android.view.View#onLayout(boolean,int,int,int,int) | |
* | |
* Creates and positions all views | |
* | |
*/ | |
@Override | |
protected void onLayout(boolean changed, int l, int t, int r, int b) { | |
super.onLayout(changed, l, t, r, b); | |
mInLayout = true; | |
layout(0, false); | |
mInLayout = false; | |
} | |
boolean mInLayout; | |
/** | |
* Creates and positions all views for this Spinner. | |
* | |
* @param delta Change in the selected position. +1 moves selection is moving to the right, | |
* so views are scrolling to the left. -1 means selection is moving to the left. | |
*/ | |
//XXX @Override | |
void layout(int delta, boolean animate) { | |
int childrenLeft = mSpinnerPadding.left; | |
int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right; | |
if (mDataChanged) { | |
handleDataChanged(); | |
} | |
// Handle the empty set by removing all views | |
if (getAdapter().getCount() == 0) { | |
resetList(); | |
return; | |
} | |
if (mNextSelectedPosition >= 0) { | |
setSelectedPositionInt(mNextSelectedPosition); | |
} | |
recycleAllViews(); | |
// Clear out old views | |
removeAllViewsInLayout(); | |
// Make selected view and position it | |
mFirstPosition = getSelectedItemPosition(); | |
View sel = makeAndAddView(mSelectedPosition); | |
int width = sel.getMeasuredWidth(); | |
int selectedOffset = childrenLeft; | |
switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { | |
case Gravity.CENTER_HORIZONTAL: | |
selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2); | |
break; | |
case Gravity.RIGHT: | |
selectedOffset = childrenLeft + childrenWidth - width; | |
break; | |
} | |
sel.offsetLeftAndRight(selectedOffset); | |
// Flush any cached views that did not get reused above | |
//XXX mRecycler.clear(); | |
invalidate(); | |
checkSelectionChanged(); | |
mDataChanged = false; | |
mNeedSync = false; | |
setNextSelectedPositionInt(mSelectedPosition); | |
} | |
/** | |
* Obtain a view, either by pulling an existing view from the recycler or | |
* by getting a new one from the adapter. If we are animating, make sure | |
* there is enough information in the view's layout parameters to animate | |
* from the old to new positions. | |
* | |
* @param position Position in the spinner for the view to obtain | |
* @return A view that has been added to the spinner | |
*/ | |
private View makeAndAddView(int position) { | |
View child; | |
if (!mDataChanged) { | |
child = mRecycler.get(position); | |
if (child != null) { | |
// Position the view | |
setUpChild(child); | |
return child; | |
} | |
} | |
// Nothing found in the recycler -- ask the adapter for a view | |
child = getAdapter().getView(position, null, this); | |
// Position the view | |
setUpChild(child); | |
return child; | |
} | |
/** | |
* Helper for makeAndAddView to set the position of a view | |
* and fill out its layout paramters. | |
* | |
* @param child The view to position | |
*/ | |
private void setUpChild(View child) { | |
// Respect layout params that are already in the view. Otherwise | |
// make some up... | |
ViewGroup.LayoutParams lp = child.getLayoutParams(); | |
if (lp == null) { | |
lp = generateDefaultLayoutParams(); | |
} | |
addViewInLayout(child, 0, lp); | |
child.setSelected(hasFocus()); | |
if (mDisableChildrenWhenDisabled) { | |
child.setEnabled(isEnabled()); | |
} | |
// Get measure specs | |
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, | |
mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height); | |
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, | |
mSpinnerPadding.left + mSpinnerPadding.right, lp.width); | |
// Measure child | |
child.measure(childWidthSpec, childHeightSpec); | |
int childLeft; | |
int childRight; | |
// Position vertically based on gravity setting | |
int childTop = mSpinnerPadding.top | |
+ ((getMeasuredHeight() - mSpinnerPadding.bottom - | |
mSpinnerPadding.top - child.getMeasuredHeight()) / 2); | |
int childBottom = childTop + child.getMeasuredHeight(); | |
int width = child.getMeasuredWidth(); | |
childLeft = 0; | |
childRight = childLeft + width; | |
child.layout(childLeft, childTop, childRight, childBottom); | |
} | |
@Override | |
public boolean performClick() { | |
boolean handled = super.performClick(); | |
if (!handled) { | |
handled = true; | |
if (!mPopup.isShowing()) { | |
mPopup.show(); | |
} | |
} | |
return handled; | |
} | |
public void onClick(DialogInterface dialog, int which) { | |
setSelection(which); | |
dialog.dismiss(); | |
} | |
/** | |
* Sets the prompt to display when the dialog is shown. | |
* @param prompt the prompt to set | |
*/ | |
public void setPrompt(CharSequence prompt) { | |
mPopup.setPromptText(prompt); | |
} | |
/** | |
* Sets the prompt to display when the dialog is shown. | |
* @param promptId the resource ID of the prompt to display when the dialog is shown | |
*/ | |
public void setPromptId(int promptId) { | |
setPrompt(getContext().getText(promptId)); | |
} | |
/** | |
* @return The prompt to display when the dialog is shown | |
*/ | |
public CharSequence getPrompt() { | |
return mPopup.getHintText(); | |
} | |
int measureContentWidth(SpinnerAdapter adapter, Drawable background) { | |
if (adapter == null) { | |
return 0; | |
} | |
int width = 0; | |
View itemView = null; | |
int itemType = 0; | |
final int widthMeasureSpec = | |
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | |
final int heightMeasureSpec = | |
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | |
// Make sure the number of items we'll measure is capped. If it's a huge data set | |
// with wildly varying sizes, oh well. | |
int start = Math.max(0, getSelectedItemPosition()); | |
final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED); | |
final int count = end - start; | |
start = Math.max(0, start - (MAX_ITEMS_MEASURED - count)); | |
for (int i = start; i < end; i++) { | |
final int positionType = adapter.getItemViewType(i); | |
if (positionType != itemType) { | |
itemType = positionType; | |
itemView = null; | |
} | |
itemView = adapter.getView(i, itemView, this); | |
if (itemView.getLayoutParams() == null) { | |
itemView.setLayoutParams(new ViewGroup.LayoutParams( | |
ViewGroup.LayoutParams.WRAP_CONTENT, | |
ViewGroup.LayoutParams.WRAP_CONTENT)); | |
} | |
itemView.measure(widthMeasureSpec, heightMeasureSpec); | |
width = Math.max(width, itemView.getMeasuredWidth()); | |
} | |
// Add background padding to measured width | |
if (background != null) { | |
background.getPadding(mTempRect); | |
width += mTempRect.left + mTempRect.right; | |
} | |
return width; | |
} | |
/** | |
* <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance | |
* into a ListAdapter.</p> | |
*/ | |
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter { | |
private SpinnerAdapter mAdapter; | |
private ListAdapter mListAdapter; | |
/** | |
* <p>Creates a new ListAdapter wrapper for the specified adapter.</p> | |
* | |
* @param adapter the Adapter to transform into a ListAdapter | |
*/ | |
public DropDownAdapter(SpinnerAdapter adapter) { | |
this.mAdapter = adapter; | |
if (adapter instanceof ListAdapter) { | |
this.mListAdapter = (ListAdapter) adapter; | |
} | |
} | |
public int getCount() { | |
return mAdapter == null ? 0 : mAdapter.getCount(); | |
} | |
public Object getItem(int position) { | |
return mAdapter == null ? null : mAdapter.getItem(position); | |
} | |
public long getItemId(int position) { | |
return mAdapter == null ? -1 : mAdapter.getItemId(position); | |
} | |
public View getView(int position, View convertView, ViewGroup parent) { | |
return getDropDownView(position, convertView, parent); | |
} | |
public View getDropDownView(int position, View convertView, ViewGroup parent) { | |
return mAdapter == null ? null : | |
mAdapter.getDropDownView(position, convertView, parent); | |
} | |
public boolean hasStableIds() { | |
return mAdapter != null && mAdapter.hasStableIds(); | |
} | |
public void registerDataSetObserver(DataSetObserver observer) { | |
if (mAdapter != null) { | |
mAdapter.registerDataSetObserver(observer); | |
} | |
} | |
public void unregisterDataSetObserver(DataSetObserver observer) { | |
if (mAdapter != null) { | |
mAdapter.unregisterDataSetObserver(observer); | |
} | |
} | |
/** | |
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. | |
* Otherwise, return true. | |
*/ | |
public boolean areAllItemsEnabled() { | |
final ListAdapter adapter = mListAdapter; | |
if (adapter != null) { | |
return adapter.areAllItemsEnabled(); | |
} else { | |
return true; | |
} | |
} | |
/** | |
* If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. | |
* Otherwise, return true. | |
*/ | |
public boolean isEnabled(int position) { | |
final ListAdapter adapter = mListAdapter; | |
if (adapter != null) { | |
return adapter.isEnabled(position); | |
} else { | |
return true; | |
} | |
} | |
public int getItemViewType(int position) { | |
return 0; | |
} | |
public int getViewTypeCount() { | |
return 1; | |
} | |
public boolean isEmpty() { | |
return getCount() == 0; | |
} | |
} | |
/** | |
* Implements some sort of popup selection interface for selecting a spinner option. | |
* Allows for different spinner modes. | |
*/ | |
private interface SpinnerPopup { | |
public void setAdapter(ListAdapter adapter); | |
/** | |
* Show the popup | |
*/ | |
public void show(); | |
/** | |
* Dismiss the popup | |
*/ | |
public void dismiss(); | |
/** | |
* @return true if the popup is showing, false otherwise. | |
*/ | |
public boolean isShowing(); | |
/** | |
* Set hint text to be displayed to the user. This should provide | |
* a description of the choice being made. | |
* @param hintText Hint text to set. | |
*/ | |
public void setPromptText(CharSequence hintText); | |
public CharSequence getHintText(); | |
} | |
private class DropdownPopup extends IcsListPopupWindow implements SpinnerPopup { | |
private CharSequence mHintText; | |
private ListAdapter mAdapter; | |
public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) { | |
super(context, attrs, 0, defStyleRes); | |
setAnchorView(IcsSpinner.this); | |
setModal(true); | |
setPromptPosition(POSITION_PROMPT_ABOVE); | |
setOnItemClickListener(new OnItemClickListener() { | |
public void onItemClick(AdapterView parent, View v, int position, long id) { | |
IcsSpinner.this.setSelection(position); | |
dismiss(); | |
} | |
}); | |
} | |
@Override | |
public void setAdapter(ListAdapter adapter) { | |
super.setAdapter(adapter); | |
mAdapter = adapter; | |
} | |
public CharSequence getHintText() { | |
return mHintText; | |
} | |
public void setPromptText(CharSequence hintText) { | |
// Hint text is ignored for dropdowns, but maintain it here. | |
mHintText = hintText; | |
} | |
@Override | |
public void show() { | |
final int spinnerPaddingLeft = IcsSpinner.this.getPaddingLeft(); | |
if (mDropDownWidth == WRAP_CONTENT) { | |
final int spinnerWidth = IcsSpinner.this.getWidth(); | |
final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight(); | |
setContentWidth(Math.max( | |
measureContentWidth((SpinnerAdapter) mAdapter, getBackground()), | |
spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight)); | |
} else if (mDropDownWidth == MATCH_PARENT) { | |
final int spinnerWidth = IcsSpinner.this.getWidth(); | |
final int spinnerPaddingRight = IcsSpinner.this.getPaddingRight(); | |
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight); | |
} else { | |
setContentWidth(mDropDownWidth); | |
} | |
final Drawable background = getBackground(); | |
int bgOffset = 0; | |
if (background != null) { | |
background.getPadding(mTempRect); | |
bgOffset = -mTempRect.left; | |
} | |
setHorizontalOffset(bgOffset + spinnerPaddingLeft); | |
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); | |
super.show(); | |
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); | |
setSelection(IcsSpinner.this.getSelectedItemPosition()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment