Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- attrs.xml
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <declare-styleable name="EcoGallery">
- <attr name="gravity" format="integer" />
- <attr name="animationDuration" format="integer" />
- <attr name="unselectedAlpha" format="float" />
- <attr name="spacing" format="dimension" />
- </declare-styleable>
- <attr name="ecoGalleryStyle" format="reference" />
- <declare-styleable name="CustomAbsSpinner">
- <attr name="entries" format="reference" />
- </declare-styleable>
- <attr name="customAbsSpinnerStyle" format="reference" />
- </resources>
- CustomAdapterView
- public abstract class CustomAdapterView<T extends Adapter> extends ViewGroup {
- /**
- * The item view type returned by {@link Adapter#getItemViewType(int)} when
- * the adapter does not want the item's view recycled.
- */
- public static final int ITEM_VIEW_TYPE_IGNORE = -1;
- /**
- * The item view type returned by {@link Adapter#getItemViewType(int)} when
- * the item is a header or footer.
- */
- public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
- /**
- * The position of the first child displayed
- */
- int mFirstPosition = 0;
- /**
- * The offset in pixels from the top of the AdapterView to the top
- * of the view to select during the next layout.
- */
- int mSpecificTop;
- /**
- * Position from which to start looking for mSyncRowId
- */
- int mSyncPosition;
- /**
- * Row id to look for when data has changed
- */
- long mSyncRowId = INVALID_ROW_ID;
- /**
- * Height of the view when mSyncPosition and mSyncRowId where set
- */
- long mSyncHeight;
- /**
- * True if we need to sync to mSyncRowId
- */
- boolean mNeedSync = false;
- /**
- * Indicates whether to sync based on the selection or position. Possible
- * values are {@link #SYNC_SELECTED_POSITION} or
- * {@link #SYNC_FIRST_POSITION}.
- */
- int mSyncMode;
- /**
- * Our height after the last layout
- */
- private int mLayoutHeight;
- /**
- * Sync based on the selected child
- */
- static final int SYNC_SELECTED_POSITION = 0;
- /**
- * Sync based on the first child displayed
- */
- static final int SYNC_FIRST_POSITION = 1;
- /**
- * Maximum amount of time to spend in {@link #findSyncPosition()}
- */
- static final int SYNC_MAX_DURATION_MILLIS = 100;
- /**
- * Indicates that this view is currently being laid out.
- */
- boolean mInLayout = false;
- /**
- * The listener that receives notifications when an item is selected.
- */
- OnItemSelectedListener mOnItemSelectedListener;
- /**
- * The listener that receives notifications when an item is clicked.
- */
- OnItemClickListener mOnItemClickListener;
- /**
- * The listener that receives notifications when an item is long clicked.
- */
- OnItemLongClickListener mOnItemLongClickListener;
- /**
- * True if the data has changed since the last layout
- */
- boolean mDataChanged;
- /**
- * The position within the adapter's data set of the item to select
- * during the next layout.
- */
- int mNextSelectedPosition = INVALID_POSITION;
- /**
- * The item id of the item to select during the next layout.
- */
- long mNextSelectedRowId = INVALID_ROW_ID;
- /**
- * The position within the adapter's data set of the currently selected item.
- */
- int mSelectedPosition = INVALID_POSITION;
- /**
- * The item id of the currently selected item.
- */
- long mSelectedRowId = INVALID_ROW_ID;
- /**
- * View to show if there are no items to show.
- */
- private View mEmptyView;
- /**
- * The number of items in the current adapter.
- */
- int mItemCount;
- /**
- * The number of items in the adapter before a data changed event occured.
- */
- int mOldItemCount;
- /**
- * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
- * number of items in the current adapter.
- */
- public static final int INVALID_POSITION = -1;
- /**
- * Represents an empty or invalid row id
- */
- public static final long INVALID_ROW_ID = Long.MIN_VALUE;
- /**
- * The last selected position we used when notifying
- */
- int mOldSelectedPosition = INVALID_POSITION;
- /**
- * The id of the last selected position we used when notifying
- */
- long mOldSelectedRowId = INVALID_ROW_ID;
- /**
- * Indicates what focusable state is requested when calling setFocusable().
- * In addition to this, this view has other criteria for actually
- * determining the focusable state (such as whether its empty or the text
- * filter is shown).
- *
- * @see #setFocusable(boolean)
- * @see #checkFocus()
- */
- private boolean mDesiredFocusableState;
- private boolean mDesiredFocusableInTouchModeState;
- private SelectionNotifier mSelectionNotifier;
- /**
- * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
- * This is used to layout the children during a layout pass.
- */
- boolean mBlockLayoutRequests = false;
- public CustomAdapterView(Context context) {
- super(context);
- }
- public CustomAdapterView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public CustomAdapterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- /**
- * Interface definition for a callback to be invoked when an item in this
- * AdapterView has been clicked.
- */
- public interface OnItemClickListener {
- /**
- * Callback method to be invoked when an item in this AdapterView has
- * been clicked.
- * <p>
- * Implementers can call getItemAtPosition(position) if they need
- * to access the data associated with the selected item.
- *
- * @param parent The AdapterView where the click happened.
- * @param view The view within the AdapterView that was clicked (this
- * will be a view provided by the adapter)
- * @param position The position of the view in the adapter.
- * @param id The row id of the item that was clicked.
- */
- void onItemClick(CustomAdapterView<?> parent, View view, int position, long id);
- }
- /**
- * Register a callback to be invoked when an item in this AdapterView has
- * been clicked.
- *
- * @param listener The callback that will be invoked.
- */
- public void setOnItemClickListener(OnItemClickListener listener) {
- mOnItemClickListener = listener;
- }
- /**
- * @return The callback to be invoked with an item in this AdapterView has
- * been clicked, or null id no callback has been set.
- */
- public final OnItemClickListener getOnItemClickListener() {
- return mOnItemClickListener;
- }
- /**
- * Call the OnItemClickListener, if it is defined.
- *
- * @param view The view within the AdapterView that was clicked.
- * @param position The position of the view in the adapter.
- * @param id The row id of the item that was clicked.
- * @return True if there was an assigned OnItemClickListener that was
- * called, false otherwise is returned.
- */
- public boolean performItemClick(View view, int position, long id) {
- if (mOnItemClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnItemClickListener.onItemClick(this, view, position, id);
- return true;
- }
- return false;
- }
- /**
- * Interface definition for a callback to be invoked when an item in this
- * view has been clicked and held.
- */
- public interface OnItemLongClickListener {
- /**
- * Callback method to be invoked when an item in this view has been
- * clicked and held.
- *
- * Implementers can call getItemAtPosition(position) if they need to access
- * the data associated with the selected item.
- *
- * @param parent The AbsListView where the click happened
- * @param view The view within the AbsListView that was clicked
- * @param position The position of the view in the list
- * @param id The row id of the item that was clicked
- *
- * @return true if the callback consumed the long click, false otherwise
- */
- boolean onItemLongClick(CustomAdapterView<?> parent, View view, int position, long id);
- }
- /**
- * Register a callback to be invoked when an item in this AdapterView has
- * been clicked and held
- *
- * @param listener The callback that will run
- */
- public void setOnItemLongClickListener(OnItemLongClickListener listener) {
- if (!isLongClickable()) {
- setLongClickable(true);
- }
- mOnItemLongClickListener = listener;
- }
- /**
- * @return The callback to be invoked with an item in this AdapterView has
- * been clicked and held, or null id no callback as been set.
- */
- public final OnItemLongClickListener getOnItemLongClickListener() {
- return mOnItemLongClickListener;
- }
- /**
- * Interface definition for a callback to be invoked when
- * an item in this view has been selected.
- */
- public interface OnItemSelectedListener {
- /**
- * Callback method to be invoked when an item in this view has been
- * selected.
- *
- * Impelmenters can call getItemAtPosition(position) if they need to access the
- * data associated with the selected item.
- *
- * @param parent The AdapterView where the selection happened
- * @param view The view within the AdapterView that was clicked
- * @param position The position of the view in the adapter
- * @param id The row id of the item that is selected
- */
- void onItemSelected(CustomAdapterView<?> parent, View view, int position, long id);
- /**
- * Callback method to be invoked when the selection disappears from this
- * view. The selection can disappear for instance when touch is activated
- * or when the adapter becomes empty.
- *
- * @param parent The AdapterView that now contains no selected item.
- */
- void onNothingSelected(CustomAdapterView<?> parent);
- }
- /**
- * Register a callback to be invoked when an item in this AdapterView has
- * been selected.
- *
- * @param listener The callback that will run
- */
- public void setOnItemSelectedListener(OnItemSelectedListener listener) {
- mOnItemSelectedListener = listener;
- }
- public final OnItemSelectedListener getOnItemSelectedListener() {
- return mOnItemSelectedListener;
- }
- /**
- * Extra menu information provided to the
- * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
- * callback when a context menu is brought up for this AdapterView.
- *
- */
- public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
- public AdapterContextMenuInfo(View targetView, int position, long id) {
- this.targetView = targetView;
- this.position = position;
- this.id = id;
- }
- /**
- * The child view for which the context menu is being displayed. This
- * will be one of the children of this AdapterView.
- */
- public View targetView;
- /**
- * The position in the adapter for which the context menu is being
- * displayed.
- */
- public int position;
- /**
- * The row id of the item for which the context menu is being displayed.
- */
- public long id;
- }
- /**
- * Returns the adapter currently associated with this widget.
- *
- * @return The adapter used to provide this view's content.
- */
- public abstract T getAdapter();
- /**
- * Sets the adapter that provides the data and the views to represent the data
- * in this widget.
- *
- * @param adapter The adapter to use to create this view's content.
- */
- public abstract void setAdapter(T adapter);
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param child Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void addView(View child) {
- throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param child Ignored.
- * @param index Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void addView(View child, int index) {
- throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param child Ignored.
- * @param params Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void addView(View child, LayoutParams params) {
- throw new UnsupportedOperationException("addView(View, LayoutParams) "
- + "is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param child Ignored.
- * @param index Ignored.
- * @param params Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void addView(View child, int index, LayoutParams params) {
- throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
- + "is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param child Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void removeView(View child) {
- throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @param index Ignored.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void removeViewAt(int index) {
- throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
- }
- /**
- * This method is not supported and throws an UnsupportedOperationException when called.
- *
- * @throws UnsupportedOperationException Every time this method is invoked.
- */
- @Override
- public void removeAllViews() {
- throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- mLayoutHeight = getHeight();
- }
- /**
- * Return the position of the currently selected item within the adapter's data set
- *
- * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
- */
- @ViewDebug.CapturedViewProperty
- public int getSelectedItemPosition() {
- return mNextSelectedPosition;
- }
- /**
- * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
- * if nothing is selected.
- */
- @ViewDebug.CapturedViewProperty
- public long getSelectedItemId() {
- return mNextSelectedRowId;
- }
- /**
- * @return The view corresponding to the currently selected item, or null
- * if nothing is selected
- */
- public abstract View getSelectedView();
- /**
- * @return The data corresponding to the currently selected item, or
- * null if there is nothing selected.
- */
- public Object getSelectedItem() {
- T adapter = getAdapter();
- int selection = getSelectedItemPosition();
- if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
- return adapter.getItem(selection);
- } else {
- return null;
- }
- }
- /**
- * @return The number of items owned by the Adapter associated with this
- * AdapterView. (This is the number of data items, which may be
- * larger than the number of visible view.)
- */
- @ViewDebug.CapturedViewProperty
- public int getCount() {
- return mItemCount;
- }
- /**
- * Get the position within the adapter's data set for the view, where view is a an adapter item
- * or a descendant of an adapter item.
- *
- * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
- * AdapterView at the time of the call.
- * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
- * if the view does not correspond to a list item (or it is not currently visible).
- */
- public int getPositionForView(View view) {
- View listItem = view;
- try {
- View v;
- while (!(v = (View) listItem.getParent()).equals(this)) {
- listItem = v;
- }
- } catch (ClassCastException e) {
- // We made it up to the window without find this list view
- return INVALID_POSITION;
- }
- // Search the children for the list item
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i).equals(listItem)) {
- return mFirstPosition + i;
- }
- }
- // Child not found!
- return INVALID_POSITION;
- }
- /**
- * Returns the position within the adapter's data set for the first item
- * displayed on screen.
- *
- * @return The position within the adapter's data set
- */
- public int getFirstVisiblePosition() {
- return mFirstPosition;
- }
- /**
- * Returns the position within the adapter's data set for the last item
- * displayed on screen.
- *
- * @return The position within the adapter's data set
- */
- public int getLastVisiblePosition() {
- return mFirstPosition + getChildCount() - 1;
- }
- /**
- * Sets the currently selected item. To support accessibility subclasses that
- * override this method must invoke the overriden super method first.
- *
- * @param position Index (starting at 0) of the data item to be selected.
- */
- public abstract void setSelection(int position);
- /**
- * Sets the view to show if the adapter is empty
- */
- public void setEmptyView(View emptyView) {
- mEmptyView = emptyView;
- final T adapter = getAdapter();
- final boolean empty = ((adapter == null) || adapter.isEmpty());
- updateEmptyStatus(empty);
- }
- /**
- * When the current adapter is empty, the AdapterView can display a special view
- * call the empty view. The empty view is used to provide feedback to the user
- * that no data is available in this AdapterView.
- *
- * @return The view to show if the adapter is empty.
- */
- public View getEmptyView() {
- return mEmptyView;
- }
- /**
- * Indicates whether this view is in filter mode. Filter mode can for instance
- * be enabled by a user when typing on the keyboard.
- *
- * @return True if the view is in filter mode, false otherwise.
- */
- boolean isInFilterMode() {
- return false;
- }
- @Override
- public void setFocusable(boolean focusable) {
- final T adapter = getAdapter();
- final boolean empty = adapter == null || adapter.getCount() == 0;
- mDesiredFocusableState = focusable;
- if (!focusable) {
- mDesiredFocusableInTouchModeState = false;
- }
- super.setFocusable(focusable && (!empty || isInFilterMode()));
- }
- @Override
- public void setFocusableInTouchMode(boolean focusable) {
- final T adapter = getAdapter();
- final boolean empty = adapter == null || adapter.getCount() == 0;
- mDesiredFocusableInTouchModeState = focusable;
- if (focusable) {
- mDesiredFocusableState = true;
- }
- super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
- }
- void checkFocus() {
- final T adapter = getAdapter();
- final boolean empty = adapter == null || adapter.getCount() == 0;
- final boolean focusable = !empty || isInFilterMode();
- // The order in which we set focusable in touch mode/focusable may matter
- // for the client, see View.setFocusableInTouchMode() comments for more
- // details
- super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
- super.setFocusable(focusable && mDesiredFocusableState);
- if (mEmptyView != null) {
- updateEmptyStatus((adapter == null) || adapter.isEmpty());
- }
- }
- /**
- * Update the status of the list based on the empty parameter. If empty is true and
- * we have an empty view, display it. In all the other cases, make sure that the listview
- * is VISIBLE and that the empty view is GONE (if it's not null).
- */
- private void updateEmptyStatus(boolean empty) {
- if (isInFilterMode()) {
- empty = false;
- }
- if (empty) {
- if (mEmptyView != null) {
- mEmptyView.setVisibility(View.VISIBLE);
- setVisibility(View.GONE);
- } else {
- // If the caller just removed our empty view, make sure the list view is visible
- setVisibility(View.VISIBLE);
- }
- // We are now GONE, so pending layouts will not be dispatched.
- // Force one here to make sure that the state of the list matches
- // the state of the adapter.
- if (mDataChanged) {
- this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
- }
- } else {
- if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
- setVisibility(View.VISIBLE);
- }
- }
- /**
- * Gets the data associated with the specified position in the list.
- *
- * @param position Which data to get
- * @return The data associated with the specified position in the list
- */
- public Object getItemAtPosition(int position) {
- T adapter = getAdapter();
- return (adapter == null || position < 0) ? null : adapter.getItem(position);
- }
- public long getItemIdAtPosition(int position) {
- T adapter = getAdapter();
- return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
- }
- @Override
- public void setOnClickListener(OnClickListener l) {
- throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
- + "You probably want setOnItemClickListener instead");
- }
- /**
- * Override to prevent freezing of any views created by the adapter.
- */
- @Override
- protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
- dispatchFreezeSelfOnly(container);
- }
- /**
- * Override to prevent thawing of any views created by the adapter.
- */
- @Override
- protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
- dispatchThawSelfOnly(container);
- }
- class AdapterDataSetObserver extends DataSetObserver {
- private Parcelable mInstanceState = null;
- @Override
- public void onChanged() {
- mDataChanged = true;
- mOldItemCount = mItemCount;
- mItemCount = getAdapter().getCount();
- // Detect the case where a cursor that was previously invalidated has
- // been repopulated with new data.
- if (CustomAdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
- && mOldItemCount == 0 && mItemCount > 0) {
- CustomAdapterView.this.onRestoreInstanceState(mInstanceState);
- mInstanceState = null;
- } else {
- rememberSyncState();
- }
- checkFocus();
- requestLayout();
- }
- @Override
- public void onInvalidated() {
- mDataChanged = true;
- if (CustomAdapterView.this.getAdapter().hasStableIds()) {
- // Remember the current state for the case where our hosting activity is being
- // stopped and later restarted
- mInstanceState = CustomAdapterView.this.onSaveInstanceState();
- }
- // Data is invalid so we should reset our state
- mOldItemCount = mItemCount;
- mItemCount = 0;
- mSelectedPosition = INVALID_POSITION;
- mSelectedRowId = INVALID_ROW_ID;
- mNextSelectedPosition = INVALID_POSITION;
- mNextSelectedRowId = INVALID_ROW_ID;
- mNeedSync = false;
- checkFocus();
- requestLayout();
- }
- public void clearSavedState() {
- mInstanceState = null;
- }
- }
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- removeCallbacks(mSelectionNotifier);
- }
- private class SelectionNotifier implements Runnable {
- public void run() {
- if (mDataChanged) {
- // Data has changed between when this SelectionNotifier
- // was posted and now. We need to wait until the AdapterView
- // has been synched to the new data.
- if (getAdapter() != null) {
- post(this);
- }
- } else {
- fireOnSelected();
- }
- }
- }
- void selectionChanged() {
- if (mOnItemSelectedListener != null) {
- if (mInLayout || mBlockLayoutRequests) {
- // If we are in a layout traversal, defer notification
- // by posting. This ensures that the view tree is
- // in a consistent state and is able to accomodate
- // new layout or invalidate requests.
- if (mSelectionNotifier == null) {
- mSelectionNotifier = new SelectionNotifier();
- }
- post(mSelectionNotifier);
- } else {
- fireOnSelected();
- }
- }
- // we fire selection events here not in View
- if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
- }
- private void fireOnSelected() {
- if (mOnItemSelectedListener == null)
- return;
- int selection = this.getSelectedItemPosition();
- if (selection >= 0) {
- View v = getSelectedView();
- mOnItemSelectedListener.onItemSelected(this, v, selection,
- getAdapter().getItemId(selection));
- } else {
- mOnItemSelectedListener.onNothingSelected(this);
- }
- }
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- boolean populated = false;
- // This is an exceptional case which occurs when a window gets the
- // focus and sends a focus event via its focused child to announce
- // current focus/selection. AdapterView fires selection but not focus
- // events so we change the event type here.
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
- event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
- // we send selection events only from AdapterView to avoid
- // generation of such event for each child
- View selectedView = getSelectedView();
- if (selectedView != null) {
- populated = selectedView.dispatchPopulateAccessibilityEvent(event);
- }
- if (!populated) {
- if (selectedView != null) {
- event.setEnabled(selectedView.isEnabled());
- }
- event.setItemCount(getCount());
- event.setCurrentItemIndex(getSelectedItemPosition());
- }
- return populated;
- }
- @Override
- protected boolean canAnimate() {
- return super.canAnimate() && mItemCount > 0;
- }
- void handleDataChanged() {
- final int count = mItemCount;
- boolean found = false;
- if (count > 0) {
- int newPos;
- // Find the row we are supposed to sync to
- if (mNeedSync) {
- // Update this first, since setNextSelectedPositionInt inspects
- // it
- mNeedSync = false;
- // See if we can find a position in the new data with the same
- // id as the old selection
- newPos = findSyncPosition();
- if (newPos >= 0) {
- // Verify that new selection is selectable
- int selectablePos = lookForSelectablePosition(newPos, true);
- if (selectablePos == newPos) {
- // Same row id is selected
- setNextSelectedPositionInt(newPos);
- found = true;
- }
- }
- }
- if (!found) {
- // Try to use the same position if we can't find matching data
- newPos = getSelectedItemPosition();
- // Pin position to the available range
- if (newPos >= count) {
- newPos = count - 1;
- }
- if (newPos < 0) {
- newPos = 0;
- }
- // Make sure we select something selectable -- first look down
- int selectablePos = lookForSelectablePosition(newPos, true);
- if (selectablePos < 0) {
- // Looking down didn't work -- try looking up
- selectablePos = lookForSelectablePosition(newPos, false);
- }
- if (selectablePos >= 0) {
- setNextSelectedPositionInt(selectablePos);
- checkSelectionChanged();
- found = true;
- }
- }
- }
- if (!found) {
- // Nothing is selected
- mSelectedPosition = INVALID_POSITION;
- mSelectedRowId = INVALID_ROW_ID;
- mNextSelectedPosition = INVALID_POSITION;
- mNextSelectedRowId = INVALID_ROW_ID;
- mNeedSync = false;
- checkSelectionChanged();
- }
- }
- void checkSelectionChanged() {
- if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
- selectionChanged();
- mOldSelectedPosition = mSelectedPosition;
- mOldSelectedRowId = mSelectedRowId;
- }
- }
- /**
- * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
- * and then alternates between moving up and moving down until 1) we find the right position, or
- * 2) we run out of time, or 3) we have looked at every position
- *
- * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
- * be found
- */
- int findSyncPosition() {
- int count = mItemCount;
- if (count == 0) {
- return INVALID_POSITION;
- }
- long idToMatch = mSyncRowId;
- int seed = mSyncPosition;
- // If there isn't a selection don't hunt for it
- if (idToMatch == INVALID_ROW_ID) {
- return INVALID_POSITION;
- }
- // Pin seed to reasonable values
- seed = Math.max(0, seed);
- seed = Math.min(count - 1, seed);
- long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
- long rowId;
- // first position scanned so far
- int first = seed;
- // last position scanned so far
- int last = seed;
- // True if we should move down on the next iteration
- boolean next = false;
- // True when we have looked at the first item in the data
- boolean hitFirst;
- // True when we have looked at the last item in the data
- boolean hitLast;
- // Get the item ID locally (instead of getItemIdAtPosition), so
- // we need the adapter
- T adapter = getAdapter();
- if (adapter == null) {
- return INVALID_POSITION;
- }
- while (SystemClock.uptimeMillis() <= endTime) {
- rowId = adapter.getItemId(seed);
- if (rowId == idToMatch) {
- // Found it!
- return seed;
- }
- hitLast = last == count - 1;
- hitFirst = first == 0;
- if (hitLast && hitFirst) {
- // Looked at everything
- break;
- }
- if (hitFirst || (next && !hitLast)) {
- // Either we hit the top, or we are trying to move down
- last++;
- seed = last;
- // Try going up next time
- next = false;
- } else if (hitLast || (!next && !hitFirst)) {
- // Either we hit the bottom, or we are trying to move up
- first--;
- seed = first;
- // Try going down next time
- next = true;
- }
- }
- return INVALID_POSITION;
- }
- /**
- * Find a position that can be selected (i.e., is not a separator).
- *
- * @param position The starting position to look at.
- * @param lookDown Whether to look down for other positions.
- * @return The next selectable position starting at position and then searching either up or
- * down. Returns {@link #INVALID_POSITION} if nothing can be found.
- */
- int lookForSelectablePosition(int position, boolean lookDown) {
- return position;
- }
- /**
- * Utility to keep mSelectedPosition and mSelectedRowId in sync
- * @param position Our current position
- */
- void setSelectedPositionInt(int position) {
- mSelectedPosition = position;
- mSelectedRowId = getItemIdAtPosition(position);
- }
- /**
- * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
- * @param position Intended value for mSelectedPosition the next time we go
- * through layout
- */
- void setNextSelectedPositionInt(int position) {
- mNextSelectedPosition = position;
- mNextSelectedRowId = getItemIdAtPosition(position);
- // If we are trying to sync to the selection, update that too
- if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
- mSyncPosition = position;
- mSyncRowId = mNextSelectedRowId;
- }
- }
- /**
- * Remember enough information to restore the screen state when the data has
- * changed.
- *
- */
- void rememberSyncState() {
- if (getChildCount() > 0) {
- mNeedSync = true;
- mSyncHeight = mLayoutHeight;
- if (mSelectedPosition >= 0) {
- // Sync the selection state
- View v = getChildAt(mSelectedPosition - mFirstPosition);
- mSyncRowId = mNextSelectedRowId;
- mSyncPosition = mNextSelectedPosition;
- if (v != null) {
- mSpecificTop = v.getTop();
- }
- mSyncMode = SYNC_SELECTED_POSITION;
- } else {
- // Sync the based on the offset of the first view
- View v = getChildAt(0);
- T adapter = getAdapter();
- if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
- mSyncRowId = adapter.getItemId(mFirstPosition);
- } else {
- mSyncRowId = NO_ID;
- }
- mSyncPosition = mFirstPosition;
- if (v != null) {
- mSpecificTop = v.getTop();
- }
- mSyncMode = SYNC_FIRST_POSITION;
- }
- }
- }
- }
- <br>
- CustomAbsSpinner: added the `add` and `get` methods to recycle bin that ignore position, modified recycler use in the class to use that.
- public abstract class CustomAbsSpinner extends CustomAdapterView<SpinnerAdapter> {
- SpinnerAdapter mAdapter;
- int mHeightMeasureSpec;
- int mWidthMeasureSpec;
- boolean mBlockLayoutRequests;
- int mSelectionLeftPadding = 0;
- int mSelectionTopPadding = 0;
- int mSelectionRightPadding = 0;
- int mSelectionBottomPadding = 0;
- Rect mSpinnerPadding = new Rect();
- View mSelectedView = null;
- Interpolator mInterpolator;
- RecycleBin mRecycler = new RecycleBin();
- private DataSetObserver mDataSetObserver;
- /** Temporary frame to hold a child View's frame rectangle */
- private Rect mTouchFrame;
- public CustomAbsSpinner(Context context) {
- super(context);
- initAbsSpinner();
- }
- public CustomAbsSpinner(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public CustomAbsSpinner(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initAbsSpinner();
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.CustomAbsSpinner, defStyle, 0);
- CharSequence[] entries = a.getTextArray(R.styleable.CustomAbsSpinner_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 paddingLeft = getPaddingLeft();
- int paddingRight = getPaddingRight();
- int paddingTop = getPaddingTop();
- int paddingBottom = getPaddingBottom();
- mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft
- : mSelectionLeftPadding;
- mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop
- : mSelectionTopPadding;
- mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight
- : mSelectionRightPadding;
- mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom
- : mSelectionBottomPadding;
- if (mDataChanged) {
- handleDataChanged();
- }
- int preferredHeight = 0;
- int preferredWidth = 0;
- boolean needsMeasuring = true;
- int selectedPosition = getSelectedItemPosition();
- if (selectedPosition >= 0 && mAdapter != null) {
- // Try looking in the recycler. (Maybe we were measured once already)
- View view = mRecycler.get();
- 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.add(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 = 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.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- void recycleAllViews() {
- int childCount = getChildCount();
- final CustomAbsSpinner.RecycleBin recycleBin = mRecycler;
- // All views go in recycler
- for (int i=0; i<childCount; i++) {
- View v = getChildAt(i);
- int index = mFirstPosition + i;
- recycleBin.put(index, v);
- }
- }
- @Override
- void handleDataChanged() {
- // FIXME -- this is called from both measure and layout.
- // This is harmless right now, but we don't want to do redundant work if
- // this gets more complicated
- super.handleDataChanged();
- }
- /**
- * 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 CustomAbsSpinner#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 SparseArray<View> mScrapHeap = new SparseArray<View>();
- public void put(int position, View v) {
- mScrapHeap.put(position, v);
- }
- public void add(int position, View v) {
- mScrapHeap.put(mScrapHeap.size(), v);
- }
- public View get() {
- if (mScrapHeap.size() < 1) return null;
- View result = mScrapHeap.valueAt(0);
- int key = mScrapHeap.keyAt(0);
- if (result != null) {
- mScrapHeap.delete(key);
- }
- return result;
- }
- 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;
- }
- View peek(int position) {
- // System.out.print("Looking for " + position);
- return mScrapHeap.get(position);
- }
- 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();
- }
- }
- }
- <br>
- EcoGallery: (it recycles)
- public class EcoGallery extends CustomAbsSpinner implements GestureDetector.OnGestureListener {
- private static final String TAG = "Gallery";
- private static final boolean localLOGV = false;
- /**
- * Duration in milliseconds from the start of a scroll during which we're
- * unsure whether the user is scrolling or flinging.
- */
- private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250;
- private static final String LOG_TAG = null;
- /**
- * Horizontal spacing between items.
- */
- private int mSpacing = 0;
- /**
- * How long the transition animation should run when a child view changes
- * position, measured in milliseconds.
- */
- private int mAnimationDuration = 200;
- /**
- * The alpha of items that are not selected.
- */
- private float mUnselectedAlpha;
- /**
- * Left most edge of a child seen so far during layout.
- */
- private int mLeftMost;
- /**
- * Right most edge of a child seen so far during layout.
- */
- private int mRightMost;
- private int mGravity;
- /**
- * Helper for detecting touch gestures.
- */
- private GestureDetector mGestureDetector;
- /**
- * The position of the item that received the user's down touch.
- */
- private int mDownTouchPosition;
- /**
- * The view of the item that received the user's down touch.
- */
- private View mDownTouchView;
- /**
- * Executes the delta scrolls from a fling or scroll movement.
- */
- private FlingRunnable mFlingRunnable = new FlingRunnable();
- /**
- * Sets mSuppressSelectionChanged = false. This is used to set it to false
- * in the future. It will also trigger a selection changed.
- */
- private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() {
- public void run() {
- mSuppressSelectionChanged = false;
- selectionChanged();
- }
- };
- /**
- * When fling runnable runs, it resets this to false. Any method along the
- * path until the end of its run() can set this to true to abort any
- * remaining fling. For example, if we've reached either the leftmost or
- * rightmost item, we will set this to true.
- */
- private boolean mShouldStopFling;
- /**
- * The currently selected item's child.
- */
- private View mSelectedChild;
- /**
- * Whether to continuously callback on the item selected listener during a
- * fling.
- */
- private boolean mShouldCallbackDuringFling = true;
- /**
- * Whether to callback when an item that is not selected is clicked.
- */
- private boolean mShouldCallbackOnUnselectedItemClick = true;
- /**
- * If true, do not callback to item selected listener.
- */
- private boolean mSuppressSelectionChanged;
- /**
- * If true, we have received the "invoke" (center or enter buttons) key
- * down. This is checked before we action on the "invoke" key up, and is
- * subsequently cleared.
- */
- private boolean mReceivedInvokeKeyDown;
- private AdapterContextMenuInfo mContextMenuInfo;
- /**
- * If true, this onScroll is the first for this user's drag (remember, a
- * drag sends many onScrolls).
- */
- private boolean mIsFirstScroll;
- /**
- * If true the reflection calls failed and this widget will behave
- * unpredictably if used further
- */
- private boolean mBroken;
- public EcoGallery(Context context) {
- this(context, null);
- }
- public EcoGallery(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.ecoGalleryStyle);
- }
- public EcoGallery(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mBroken = true;
- mGestureDetector = new GestureDetector(context, this);
- mGestureDetector.setIsLongpressEnabled(true);
- TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.EcoGallery, defStyle, 0);
- int index = a.getInt(R.styleable.EcoGallery_gravity, -1);
- if (index >= 0) {
- setGravity(index);
- }
- int animationDuration =
- a.getInt(R.styleable.EcoGallery_animationDuration, -1);
- if (animationDuration > 0) {
- setAnimationDuration(animationDuration);
- }
- int spacing =
- a.getDimensionPixelOffset(R.styleable.EcoGallery_spacing, 0);
- setSpacing(spacing);
- float unselectedAlpha = a.getFloat(
- R.styleable.EcoGallery_unselectedAlpha, 0.5f);
- setUnselectedAlpha(unselectedAlpha);
- a.recycle();
- // We draw the selected item last (because otherwise the item to the
- // right overlaps it)
- int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
- int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
- Class vgClass = ViewGroup.class;
- try {
- Field childDrawingOrder = vgClass
- .getDeclaredField("FLAG_USE_CHILD_DRAWING_ORDER");
- Field supportStaticTrans = vgClass
- .getDeclaredField("FLAG_SUPPORT_STATIC_TRANSFORMATIONS");
- childDrawingOrder.setAccessible(true);
- supportStaticTrans.setAccessible(true);
- FLAG_USE_CHILD_DRAWING_ORDER = childDrawingOrder.getInt(this);
- FLAG_SUPPORT_STATIC_TRANSFORMATIONS = supportStaticTrans.getInt(this);
- } catch (NoSuchFieldException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- } catch (IllegalAccessException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- }
- try {
- // set new group flags
- Field groupFlags = vgClass.getDeclaredField("mGroupFlags");
- groupFlags.setAccessible(true);
- int groupFlagsValue = groupFlags.getInt(this);
- groupFlagsValue |= FLAG_USE_CHILD_DRAWING_ORDER;
- groupFlagsValue |= FLAG_SUPPORT_STATIC_TRANSFORMATIONS;
- groupFlags.set(this, groupFlagsValue);
- // working!
- mBroken = false;
- } catch (NoSuchFieldException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- } catch (IllegalAccessException e) {
- Log.e(LOG_TAG, e.getMessage(), e);
- }
- }
- /**
- * @return Whether the widget is broken or working (functional)
- */
- public boolean isBroken() {
- return mBroken;
- }
- /**
- * Whether or not to callback on any {@link #getOnItemSelectedListener()}
- * while the items are being flinged. If false, only the final selected item
- * will cause the callback. If true, all items between the first and the
- * final will cause callbacks.
- *
- * @param shouldCallback Whether or not to callback on the listener while
- * the items are being flinged.
- */
- public void setCallbackDuringFling(boolean shouldCallback) {
- mShouldCallbackDuringFling = shouldCallback;
- }
- /**
- * Whether or not to callback when an item that is not selected is clicked.
- * If false, the item will become selected (and re-centered). If true, the
- * {@link #getOnItemClickListener()} will get the callback.
- *
- * @param shouldCallback Whether or not to callback on the listener when a
- * item that is not selected is clicked.
- * @hide
- */
- public void setCallbackOnUnselectedItemClick(boolean shouldCallback) {
- mShouldCallbackOnUnselectedItemClick = shouldCallback;
- }
- /**
- * Sets how long the transition animation should run when a child view
- * changes position. Only relevant if animation is turned on.
- *
- * @param animationDurationMillis The duration of the transition, in
- * milliseconds.
- *
- * @attr ref android.R.styleable#Gallery_animationDuration
- */
- public void setAnimationDuration(int animationDurationMillis) {
- mAnimationDuration = animationDurationMillis;
- }
- /**
- * Sets the spacing between items in a Gallery
- *
- * @param spacing The spacing in pixels between items in the Gallery
- *
- * @attr ref android.R.styleable#Gallery_spacing
- */
- public void setSpacing(int spacing) {
- mSpacing = spacing;
- }
- /**
- * Sets the alpha of items that are not selected in the Gallery.
- *
- * @param unselectedAlpha the alpha for the items that are not selected.
- *
- * @attr ref android.R.styleable#Gallery_unselectedAlpha
- */
- public void setUnselectedAlpha(float unselectedAlpha) {
- mUnselectedAlpha = unselectedAlpha;
- }
- @Override
- protected boolean getChildStaticTransformation(View child, Transformation t) {
- t.clear();
- t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha);
- return true;
- }
- @Override
- protected int computeHorizontalScrollExtent() {
- // Only 1 item is considered to be selected
- return 1;
- }
- @Override
- protected int computeHorizontalScrollOffset() {
- // Current scroll position is the same as the selected position
- return mSelectedPosition;
- }
- @Override
- protected int computeHorizontalScrollRange() {
- // Scroll range is the same as the item count
- return mItemCount;
- }
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof LayoutParams;
- }
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- /*
- * Gallery expects EcoGallery.LayoutParams.
- */
- return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- /*
- * Remember that we are in layout to prevent more layout request from
- * being generated.
- */
- mInLayout = true;
- layout(0, false);
- mInLayout = false;
- }
- @Override
- int getChildHeight(View child) {
- return child.getMeasuredHeight();
- }
- /**
- * Tracks a motion scroll. In reality, this is used to do just about any
- * movement to items (touch scroll, arrow-key scroll, set an item as selected).
- *
- * @param deltaX Change in X from the previous event.
- */
- void trackMotionScroll(int deltaX) {
- if (getChildCount() == 0) {
- return;
- }
- boolean toLeft = deltaX < 0;
- int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
- if (limitedDeltaX != deltaX) {
- // The above call returned a limited amount, so stop any scrolls/flings
- mFlingRunnable.endFling(false);
- onFinishedMovement();
- }
- offsetChildrenLeftAndRight(limitedDeltaX);
- detachOffScreenChildren(toLeft);
- if (toLeft) {
- // If moved left, there will be empty space on the right
- fillToGalleryRight();
- } else {
- // Similarly, empty space on the left
- fillToGalleryLeft();
- }
- setSelectionToCenterChild();
- invalidate();
- }
- int getLimitedMotionScrollAmount(boolean motionToLeft, int deltaX) {
- int extremeItemPosition = motionToLeft ? mItemCount - 1 : 0;
- View extremeChild = getChildAt(extremeItemPosition - mFirstPosition);
- if (extremeChild == null) {
- return deltaX;
- }
- int extremeChildCenter = getCenterOfView(extremeChild);
- int galleryCenter = getCenterOfGallery();
- if (motionToLeft) {
- if (extremeChildCenter <= galleryCenter) {
- // The extreme child is past his boundary point!
- return 0;
- }
- } else {
- if (extremeChildCenter >= galleryCenter) {
- // The extreme child is past his boundary point!
- return 0;
- }
- }
- int centerDifference = galleryCenter - extremeChildCenter;
- return motionToLeft
- ? Math.max(centerDifference, deltaX)
- : Math.min(centerDifference, deltaX);
- }
- /**
- * Offset the horizontal location of all children of this view by the
- * specified number of pixels.
- *
- * @param offset the number of pixels to offset
- */
- private void offsetChildrenLeftAndRight(int offset) {
- for (int i = getChildCount() - 1; i >= 0; i--) {
- getChildAt(i).offsetLeftAndRight(offset);
- }
- }
- /**
- * @return The center of this Gallery.
- */
- private int getCenterOfGallery() {
- int paddingLeft = getPaddingLeft();
- return (getWidth() - paddingLeft - getPaddingRight()) / 2 + paddingLeft;
- }
- /**
- * @return The center of the given view.
- */
- private static int getCenterOfView(View view) {
- return view.getLeft() + view.getWidth() / 2;
- }
- /**
- * Detaches children that are off the screen (i.e.: Gallery bounds).
- *
- * @param toLeft Whether to detach children to the left of the Gallery, or
- * to the right.
- */
- private void detachOffScreenChildren(boolean toLeft) {
- int numChildren = getChildCount();
- int firstPosition = mFirstPosition;
- int start = 0;
- int count = 0;
- if (toLeft) {
- final int galleryLeft = getPaddingLeft();
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child.getRight() >= galleryLeft) {
- break;
- } else {
- count++;
- mRecycler.add(firstPosition + i, child);
- }
- }
- } else {
- final int galleryRight = getWidth() - getPaddingRight();
- for (int i = numChildren - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- if (child.getLeft() <= galleryRight) {
- break;
- } else {
- start = i;
- count++;
- mRecycler.add(firstPosition + i, child);
- }
- }
- }
- detachViewsFromParent(start, count);
- if (toLeft) {
- mFirstPosition += count;
- }
- }
- /**
- * Scrolls the items so that the selected item is in its 'slot' (its center
- * is the gallery's center).
- */
- private void scrollIntoSlots() {
- if (getChildCount() == 0 || mSelectedChild == null) return;
- int selectedCenter = getCenterOfView(mSelectedChild);
- int targetCenter = getCenterOfGallery();
- int scrollAmount = targetCenter - selectedCenter;
- if (scrollAmount != 0) {
- mFlingRunnable.startUsingDistance(scrollAmount);
- } else {
- onFinishedMovement();
- }
- }
- private void onFinishedMovement() {
- if (mSuppressSelectionChanged) {
- mSuppressSelectionChanged = false;
- // We haven't been callbacking during the fling, so do it now
- super.selectionChanged();
- }
- invalidate();
- }
- @Override
- void selectionChanged() {
- if (!mSuppressSelectionChanged) {
- super.selectionChanged();
- }
- }
- /**
- * Looks for the child that is closest to the center and sets it as the
- * selected child.
- */
- private void setSelectionToCenterChild() {
- View selView = mSelectedChild;
- if (mSelectedChild == null) return;
- int galleryCenter = getCenterOfGallery();
- // Common case where the current selected position is correct
- if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) {
- return;
- }
- // TODO better search
- int closestEdgeDistance = Integer.MAX_VALUE;
- int newSelectedChildIndex = 0;
- for (int i = getChildCount() - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (child.getLeft() <= galleryCenter && child.getRight() >= galleryCenter) {
- // This child is in the center
- newSelectedChildIndex = i;
- break;
- }
- int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - galleryCenter),
- Math.abs(child.getRight() - galleryCenter));
- if (childClosestEdgeDistance < closestEdgeDistance) {
- closestEdgeDistance = childClosestEdgeDistance;
- newSelectedChildIndex = i;
- }
- }
- int newPos = mFirstPosition + newSelectedChildIndex;
- if (newPos != mSelectedPosition) {
- setSelectedPositionInt(newPos);
- setNextSelectedPositionInt(newPos);
- checkSelectionChanged();
- }
- }
- /**
- * Creates and positions all views for this Gallery.
- * <p>
- * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes
- * care of repositioning, adding, and removing children.
- *
- * @param delta Change in the selected position. +1 means the selection is
- * moving to the right, so views are scrolling to the left. -1
- * means the selection is moving to the left.
- */
- @Override
- void layout(int delta, boolean animate) {
- int childrenLeft = mSpinnerPadding.left;
- int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
- if (mDataChanged) {
- handleDataChanged();
- }
- // Handle an empty gallery by removing all views.
- if (mItemCount == 0) {
- resetList();
- return;
- }
- // Update to the new selected position.
- if (mNextSelectedPosition >= 0) {
- setSelectedPositionInt(mNextSelectedPosition);
- }
- // All views go in recycler while we are in layout
- recycleAllViews();
- // Clear out old views
- detachAllViewsFromParent();
- /*
- * These will be used to give initial positions to views entering the
- * gallery as we scroll
- */
- mRightMost = 0;
- mLeftMost = 0;
- // Make selected view and center it
- /*
- * mFirstPosition will be decreased as we add views to the left later
- * on. The 0 for x will be offset in a couple lines down.
- */
- mFirstPosition = mSelectedPosition;
- View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
- // Put the selected child in the center
- int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2);
- sel.offsetLeftAndRight(selectedOffset);
- fillToGalleryRight();
- fillToGalleryLeft();
- invalidate();
- checkSelectionChanged();
- mDataChanged = false;
- mNeedSync = false;
- setNextSelectedPositionInt(mSelectedPosition);
- updateSelectedItemMetadata();
- }
- private void fillToGalleryLeft() {
- int itemSpacing = mSpacing;
- int galleryLeft = getPaddingLeft();
- // Set state for initial iteration
- View prevIterationView = getChildAt(0);
- int curPosition;
- int curRightEdge;
- if (prevIterationView != null) {
- curPosition = mFirstPosition - 1;
- curRightEdge = prevIterationView.getLeft() - itemSpacing;
- } else {
- // No children available!
- curPosition = 0;
- curRightEdge = getRight() - getLeft() - getPaddingRight();
- mShouldStopFling = true;
- }
- while (curRightEdge > galleryLeft && curPosition >= 0) {
- prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
- curRightEdge, false);
- // Remember some state
- mFirstPosition = curPosition;
- // Set state for next iteration
- curRightEdge = prevIterationView.getLeft() - itemSpacing;
- curPosition--;
- }
- }
- private void fillToGalleryRight() {
- int itemSpacing = mSpacing;
- int galleryRight = getRight() - getLeft() - getPaddingRight();
- int numChildren = getChildCount();
- int numItems = mItemCount;
- // Set state for initial iteration
- View prevIterationView = getChildAt(numChildren - 1);
- int curPosition;
- int curLeftEdge;
- if (prevIterationView != null) {
- curPosition = mFirstPosition + numChildren;
- curLeftEdge = prevIterationView.getRight() + itemSpacing;
- } else {
- mFirstPosition = curPosition = mItemCount - 1;
- curLeftEdge = getPaddingLeft();
- mShouldStopFling = true;
- }
- while (curLeftEdge < galleryRight && curPosition < numItems) {
- prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition,
- curLeftEdge, true);
- // Set state for next iteration
- curLeftEdge = prevIterationView.getRight() + itemSpacing;
- curPosition++;
- }
- }
- /**
- * 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 gallery for the view to obtain
- * @param offset Offset from the selected position
- * @param x X-coordinate indicating where this view should be placed. This
- * will either be the left or right edge of the view, depending on
- * the fromLeft parameter
- * @param fromLeft Are we positioning views based on the left edge? (i.e.,
- * building from left to right)?
- * @return A view that has been added to the gallery
- */
- private View makeAndAddView(int position, int offset, int x,
- boolean fromLeft) {
- View child;
- child = mRecycler.get();
- // pass child as convertview
- child = mAdapter.getView(position, child, this);
- // Position the view
- setUpChild(child, offset, x, fromLeft);
- return child;
- }
- /**
- * Helper for makeAndAddView to set the position of a view and fill out its
- * layout paramters.
- *
- * @param child The view to position
- * @param offset Offset from the selected position
- * @param x X-coordintate indicating where this view should be placed. This
- * will either be the left or right edge of the view, depending on
- * the fromLeft paramter
- * @param fromLeft Are we posiitoning views based on the left edge? (i.e.,
- * building from left to right)?
- */
- private void setUpChild(View child, int offset, int x, boolean fromLeft) {
- // Respect layout params that are already in the view. Otherwise
- // make some up...
- LayoutParams lp = (LayoutParams)
- child.getLayoutParams();
- if (lp == null) {
- lp = (LayoutParams) generateDefaultLayoutParams();
- }
- addViewInLayout(child, fromLeft ? -1 : 0, lp);
- child.setSelected(offset == 0);
- // 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 = calculateTop(child, true);
- int childBottom = childTop + child.getMeasuredHeight();
- int width = child.getMeasuredWidth();
- if (fromLeft) {
- childLeft = x;
- childRight = childLeft + width;
- } else {
- childLeft = x - width;
- childRight = x;
- }
- child.layout(childLeft, childTop, childRight, childBottom);
- }
- /**
- * Figure out vertical placement based on mGravity
- *
- * @param child Child to place
- * @return Where the top of the child should be
- */
- private int calculateTop(View child, boolean duringLayout) {
- int myHeight = duringLayout ? getMeasuredHeight() : getHeight();
- int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight();
- int childTop = 0;
- switch (mGravity) {
- case Gravity.TOP:
- childTop = mSpinnerPadding.top;
- break;
- case Gravity.CENTER_VERTICAL:
- int availableSpace = myHeight - mSpinnerPadding.bottom
- - mSpinnerPadding.top - childHeight;
- childTop = mSpinnerPadding.top + (availableSpace / 2);
- break;
- case Gravity.BOTTOM:
- childTop = myHeight - mSpinnerPadding.bottom - childHeight;
- break;
- }
- return childTop;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // Give everything to the gesture detector
- boolean retValue = mGestureDetector.onTouchEvent(event);
- int action = event.getAction();
- if (action == MotionEvent.ACTION_UP) {
- // Helper method for lifted finger
- onUp();
- } else if (action == MotionEvent.ACTION_CANCEL) {
- onCancel();
- }
- return retValue;
- }
- /**
- * {@inheritDoc}
- */
- public boolean onSingleTapUp(MotionEvent e) {
- if (mDownTouchPosition >= 0) {
- // An item tap should make it selected, so scroll to this child.
- scrollToChild(mDownTouchPosition - mFirstPosition);
- // Also pass the click so the client knows, if it wants to.
- if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) {
- performItemClick(mDownTouchView, mDownTouchPosition, mAdapter
- .getItemId(mDownTouchPosition));
- }
- return true;
- }
- return false;
- }
- /**
- * {@inheritDoc}
- */
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (!mShouldCallbackDuringFling) {
- // We want to suppress selection changes
- // Remove any future code to set mSuppressSelectionChanged = false
- removeCallbacks(mDisableSuppressSelectionChangedRunnable);
- // This will get reset once we scroll into slots
- if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
- }
- // Fling the gallery!
- mFlingRunnable.startUsingVelocity((int) -velocityX);
- return true;
- }
- /**
- * {@inheritDoc}
- */
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- if (localLOGV) Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
- /*
- * Now's a good time to tell our parent to stop intercepting our events!
- * The user has moved more than the slop amount, since GestureDetector
- * ensures this before calling this method. Also, if a parent is more
- * interested in this touch's events than we are, it would have
- * intercepted them by now (for example, we can assume when a Gallery is
- * in the ListView, a vertical scroll would not end up in this method
- * since a ListView would have intercepted it by now).
- */
- getParent().requestDisallowInterceptTouchEvent(true);
- // As the user scrolls, we want to callback selection changes so related-
- // info on the screen is up-to-date with the gallery's selection
- if (!mShouldCallbackDuringFling) {
- if (mIsFirstScroll) {
- /*
- * We're not notifying the client of selection changes during
- * the fling, and this scroll could possibly be a fling. Don't
- * do selection changes until we're sure it is not a fling.
- */
- if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true;
- postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
- }
- } else {
- if (mSuppressSelectionChanged) mSuppressSelectionChanged = false;
- }
- // Track the motion
- trackMotionScroll(-1 * (int) distanceX);
- mIsFirstScroll = false;
- return true;
- }
- /**
- * {@inheritDoc}
- */
- public boolean onDown(MotionEvent e) {
- // Kill any existing fling/scroll
- mFlingRunnable.stop(false);
- // Get the item's view that was touched
- mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
- if (mDownTouchPosition >= 0) {
- mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
- mDownTouchView.setPressed(true);
- }
- // Reset the multiple-scroll tracking state
- mIsFirstScroll = true;
- // Must return true to get matching events for this down event.
- return true;
- }
- /**
- * Called when a touch event's action is MotionEvent.ACTION_UP.
- */
- void onUp() {
- if (mFlingRunnable.mScroller.isFinished()) {
- scrollIntoSlots();
- }
- dispatchUnpress();
- }
- /**
- * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
- */
- void onCancel() {
- onUp();
- }
- /**
- * {@inheritDoc}
- */
- public void onLongPress(MotionEvent e) {
- if (mDownTouchPosition < 0) {
- return;
- }
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- long id = getItemIdAtPosition(mDownTouchPosition);
- dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
- }
- // Unused methods from GestureDetector.OnGestureListener below
- /**
- * {@inheritDoc}
- */
- public void onShowPress(MotionEvent e) {
- }
- // Unused methods from GestureDetector.OnGestureListener above
- private void dispatchPress(View child) {
- if (child != null) {
- child.setPressed(true);
- }
- setPressed(true);
- }
- private void dispatchUnpress() {
- for (int i = getChildCount() - 1; i >= 0; i--) {
- getChildAt(i).setPressed(false);
- }
- setPressed(false);
- }
- @Override
- public void dispatchSetSelected(boolean selected) {
- /*
- * We don't want to pass the selected state given from its parent to its
- * children since this widget itself has a selected state to give to its
- * children.
- */
- }
- @Override
- protected void dispatchSetPressed(boolean pressed) {
- // Show the pressed state on the selected child
- if (mSelectedChild != null) {
- mSelectedChild.setPressed(pressed);
- }
- }
- @Override
- protected ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
- @Override
- public boolean showContextMenuForChild(View originalView) {
- final int longPressPosition = getPositionForView(originalView);
- if (longPressPosition < 0) {
- return false;
- }
- final long longPressId = mAdapter.getItemId(longPressPosition);
- return dispatchLongPress(originalView, longPressPosition, longPressId);
- }
- @Override
- public boolean showContextMenu() {
- if (isPressed() && mSelectedPosition >= 0) {
- int index = mSelectedPosition - mFirstPosition;
- View v = getChildAt(index);
- return dispatchLongPress(v, mSelectedPosition, mSelectedRowId);
- }
- return false;
- }
- private boolean dispatchLongPress(View view, int position, long id) {
- boolean handled = false;
- if (mOnItemLongClickListener != null) {
- handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView,
- mDownTouchPosition, id);
- }
- if (!handled) {
- mContextMenuInfo = new AdapterContextMenuInfo(view, position, id);
- handled = super.showContextMenuForChild(this);
- }
- if (handled) {
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
- return handled;
- }
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- // Gallery steals all key events
- return event.dispatch(this, null, null);
- }
- /**
- * Handles left, right, and clicking
- * @see android.view.View#onKeyDown
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (movePrevious()) {
- playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT);
- }
- return true;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (moveNext()) {
- playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
- }
- return true;
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- mReceivedInvokeKeyDown = true;
- // fallthrough to default handling
- }
- return super.onKeyDown(keyCode, event);
- }
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER: {
- if (mReceivedInvokeKeyDown) {
- if (mItemCount > 0) {
- dispatchPress(mSelectedChild);
- postDelayed(new Runnable() {
- public void run() {
- dispatchUnpress();
- }
- }, ViewConfiguration.getPressedStateDuration());
- int selectedIndex = mSelectedPosition - mFirstPosition;
- performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
- .getItemId(mSelectedPosition));
- }
- }
- // Clear the flag
- mReceivedInvokeKeyDown = false;
- return true;
- }
- }
- return super.onKeyUp(keyCode, event);
- }
- boolean movePrevious() {
- if (mItemCount > 0 && mSelectedPosition > 0) {
- scrollToChild(mSelectedPosition - mFirstPosition - 1);
- return true;
- } else {
- return false;
- }
- }
- boolean moveNext() {
- if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
- scrollToChild(mSelectedPosition - mFirstPosition + 1);
- return true;
- } else {
- return false;
- }
- }
- private boolean scrollToChild(int childPosition) {
- View child = getChildAt(childPosition);
- if (child != null) {
- int distance = getCenterOfGallery() - getCenterOfView(child);
- mFlingRunnable.startUsingDistance(distance);
- return true;
- }
- return false;
- }
- @Override
- void setSelectedPositionInt(int position) {
- super.setSelectedPositionInt(position);
- // Updates any metadata we keep about the selected item.
- updateSelectedItemMetadata();
- }
- private void updateSelectedItemMetadata() {
- View oldSelectedChild = mSelectedChild;
- View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition);
- if (child == null) {
- return;
- }
- child.setSelected(true);
- child.setFocusable(true);
- if (hasFocus()) {
- child.requestFocus();
- }
- // We unfocus the old child down here so the above hasFocus check
- // returns true
- if (oldSelectedChild != null) {
- // Make sure its drawable state doesn't contain 'selected'
- oldSelectedChild.setSelected(false);
- // Make sure it is not focusable anymore, since otherwise arrow keys
- // can make this one be focused
- oldSelectedChild.setFocusable(false);
- }
- }
- /**
- * Describes how the child views are aligned.
- * @param gravity
- *
- * @attr ref android.R.styleable#Gallery_gravity
- */
- public void setGravity(int gravity)
- {
- if (mGravity != gravity) {
- mGravity = gravity;
- requestLayout();
- }
- }
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- int selectedIndex = mSelectedPosition - mFirstPosition;
- // Just to be safe
- if (selectedIndex < 0) return i;
- if (i == childCount - 1) {
- // Draw the selected child last
- return selectedIndex;
- } else if (i >= selectedIndex) {
- // Move the children to the right of the selected child earlier one
- return i + 1;
- } else {
- // Keep the children to the left of the selected child the same
- return i;
- }
- }
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- /*
- * The gallery shows focus by focusing the selected item. So, give
- * focus to our selected item instead. We steal keys from our
- * selected item elsewhere.
- */
- if (gainFocus && mSelectedChild != null) {
- mSelectedChild.requestFocus(direction);
- }
- }
- /**
- * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to
- * initiate a fling. Each frame of the fling is handled in {@link #run()}.
- * A FlingRunnable will keep re-posting itself until the fling is done.
- *
- */
- private class FlingRunnable implements Runnable {
- /**
- * Tracks the decay of a fling scroll
- */
- private Scroller mScroller;
- /**
- * X value reported by mScroller on the previous fling
- */
- private int mLastFlingX;
- public FlingRunnable() {
- mScroller = new Scroller(getContext());
- }
- private void startCommon() {
- // Remove any pending flings
- removeCallbacks(this);
- }
- public void startUsingVelocity(int initialVelocity) {
- if (initialVelocity == 0) return;
- startCommon();
- int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
- mLastFlingX = initialX;
- mScroller.fling(initialX, 0, initialVelocity, 0,
- 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
- post(this);
- }
- public void startUsingDistance(int distance) {
- if (distance == 0) return;
- startCommon();
- mLastFlingX = 0;
- mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration);
- post(this);
- }
- public void stop(boolean scrollIntoSlots) {
- removeCallbacks(this);
- endFling(scrollIntoSlots);
- }
- private void endFling(boolean scrollIntoSlots) {
- /*
- * Force the scroller's status to finished (without setting its
- * position to the end)
- */
- mScroller.forceFinished(true);
- if (scrollIntoSlots) scrollIntoSlots();
- }
- public void run() {
- if (mItemCount == 0) {
- endFling(true);
- return;
- }
- mShouldStopFling = false;
- final Scroller scroller = mScroller;
- boolean more = scroller.computeScrollOffset();
- final int x = scroller.getCurrX();
- // Flip sign to convert finger direction to list items direction
- // (e.g. finger moving down means list is moving towards the top)
- int delta = mLastFlingX - x;
- // Pretend that each frame of a fling scroll is a touch scroll
- if (delta > 0) {
- // Moving towards the left. Use first view as mDownTouchPosition
- mDownTouchPosition = mFirstPosition;
- // Don't fling more than 1 screen
- delta = Math.min(getWidth() - getPaddingLeft() - getPaddingRight() - 1, delta);
- } else {
- // Moving towards the right. Use last view as mDownTouchPosition
- int offsetToLast = getChildCount() - 1;
- mDownTouchPosition = mFirstPosition + offsetToLast;
- // Don't fling more than 1 screen
- delta = Math.max(-(getWidth() - getPaddingRight() - getPaddingLeft() - 1), delta);
- }
- trackMotionScroll(delta);
- if (more && !mShouldStopFling) {
- mLastFlingX = x;
- post(this);
- } else {
- endFling(true);
- }
- }
- }
- /**
- * Gallery extends LayoutParams to provide a place to hold current
- * Transformation information along with previous position/transformation
- * info.
- *
- */
- public static class LayoutParams extends ViewGroup.LayoutParams {
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- }
- public LayoutParams(int w, int h) {
- super(w, h);
- }
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
- }
- }
- You'll also need to copy `simple_spinner_item` and `simple_spinner_dropdown_item` from the SDK layout folder into yours.
- Then to reference it layout XML:
- <com.blah.EcoGallery
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:blah="http://schemas.android.com/apk/res/com.blah"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- blah:spacing="0px"
- blah:unselectedAlpha="0.5"
- blah:animationDuration="200"
- blah:gravity="center_horizontal"
- />
Add Comment
Please, Sign In to add comment