Java tutorial
/* 50AH-code ========= 50 Android Hacks (http://manning.com/sessa/) book source code Copyright (c) 2012 Manning Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * 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.database.DataSetObserver; import android.graphics.Rect; import android.os.Build; 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.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 IcsAdapterView<SpinnerAdapter> { private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 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; final int mPaddingLeft = getPaddingLeft(); final int mPaddingTop = getPaddingTop(); final int mPaddingRight = getPaddingRight(); final 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()); if (IS_HONEYCOMB) { heightSize = resolveSizeAndState(preferredHeight, heightMeasureSpec, 0); widthSize = resolveSizeAndState(preferredWidth, widthMeasureSpec, 0); } else { heightSize = resolveSize(preferredHeight, heightMeasureSpec); widthSize = resolveSize(preferredWidth, widthMeasureSpec); } 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() { 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 mItemCount; } /** * 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(); } } }