2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
19 import android.content.Context;
20 import android.database.DataSetObserver;
21 import android.os.Parcelable;
22 import android.os.SystemClock;
23 import android.util.AttributeSet;
24 import android.util.SparseArray;
25 import android.view.ContextMenu;
26 import android.view.ContextMenu.ContextMenuInfo;
27 import android.view.SoundEffectConstants;
28 import android.view.View;
29 import android.view.ViewDebug;
30 import android.view.ViewGroup;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.view.accessibility.AccessibilityNodeInfo;
36 * An AdapterView is a view whose children are determined by an {@link Adapter}.
39 * See {@link ListView}, {@link GridView}, {@link Spinner} and
40 * {@link Gallery} for commonly used subclasses of AdapterView.
42 public abstract class AdapterView<T extends Adapter> extends ViewGroup {
45 * The item view type returned by {@link Adapter#getItemViewType(int)} when
46 * the adapter does not want the item's view recycled.
48 public static final int ITEM_VIEW_TYPE_IGNORE = -1;
51 * The item view type returned by {@link Adapter#getItemViewType(int)} when
52 * the item is a header or footer.
54 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
57 * The position of the first child displayed
59 @ViewDebug.ExportedProperty(category = "scrolling")
60 int mFirstPosition = 0;
63 * The offset in pixels from the top of the AdapterView to the top
64 * of the view to select during the next layout.
69 * Position from which to start looking for mSyncRowId
74 * Row id to look for when data has changed
76 long mSyncRowId = INVALID_ROW_ID;
79 * Height of the view when mSyncPosition and mSyncRowId where set
84 * True if we need to sync to mSyncRowId
86 boolean mNeedSync = false;
89 * Indicates whether to sync based on the selection or position. Possible
90 * values are {@link #SYNC_SELECTED_POSITION} or
91 * {@link #SYNC_FIRST_POSITION}.
96 * Our height after the last layout
98 private int mLayoutHeight;
101 * Sync based on the selected child
103 static final int SYNC_SELECTED_POSITION = 0;
106 * Sync based on the first child displayed
108 static final int SYNC_FIRST_POSITION = 1;
111 * Maximum amount of time to spend in {@link #findSyncPosition()}
113 static final int SYNC_MAX_DURATION_MILLIS = 100;
116 * Indicates that this view is currently being laid out.
118 boolean mInLayout = false;
121 * The listener that receives notifications when an item is selected.
123 OnItemSelectedListener mOnItemSelectedListener;
126 * The listener that receives notifications when an item is clicked.
128 OnItemClickListener mOnItemClickListener;
131 * The listener that receives notifications when an item is long clicked.
133 OnItemLongClickListener mOnItemLongClickListener;
136 * True if the data has changed since the last layout
138 boolean mDataChanged;
141 * The position within the adapter's data set of the item to select
142 * during the next layout.
144 @ViewDebug.ExportedProperty(category = "list")
145 int mNextSelectedPosition = INVALID_POSITION;
148 * The item id of the item to select during the next layout.
150 long mNextSelectedRowId = INVALID_ROW_ID;
153 * The position within the adapter's data set of the currently selected item.
155 @ViewDebug.ExportedProperty(category = "list")
156 int mSelectedPosition = INVALID_POSITION;
159 * The item id of the currently selected item.
161 long mSelectedRowId = INVALID_ROW_ID;
164 * View to show if there are no items to show.
166 private View mEmptyView;
169 * The number of items in the current adapter.
171 @ViewDebug.ExportedProperty(category = "list")
175 * The number of items in the adapter before a data changed event occurred.
180 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
181 * number of items in the current adapter.
183 public static final int INVALID_POSITION = -1;
186 * Represents an empty or invalid row id
188 public static final long INVALID_ROW_ID = Long.MIN_VALUE;
191 * The last selected position we used when notifying
193 int mOldSelectedPosition = INVALID_POSITION;
196 * The id of the last selected position we used when notifying
198 long mOldSelectedRowId = INVALID_ROW_ID;
201 * Indicates what focusable state is requested when calling setFocusable().
202 * In addition to this, this view has other criteria for actually
203 * determining the focusable state (such as whether its empty or the text
206 * @see #setFocusable(boolean)
209 private boolean mDesiredFocusableState;
210 private boolean mDesiredFocusableInTouchModeState;
212 private SelectionNotifier mSelectionNotifier;
214 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
215 * This is used to layout the children during a layout pass.
217 boolean mBlockLayoutRequests = false;
219 public AdapterView(Context context) {
223 public AdapterView(Context context, AttributeSet attrs) {
224 super(context, attrs);
227 public AdapterView(Context context, AttributeSet attrs, int defStyle) {
228 super(context, attrs, defStyle);
232 * Interface definition for a callback to be invoked when an item in this
233 * AdapterView has been clicked.
235 public interface OnItemClickListener {
238 * Callback method to be invoked when an item in this AdapterView has
241 * Implementers can call getItemAtPosition(position) if they need
242 * to access the data associated with the selected item.
244 * @param parent The AdapterView where the click happened.
245 * @param view The view within the AdapterView that was clicked (this
246 * will be a view provided by the adapter)
247 * @param position The position of the view in the adapter.
248 * @param id The row id of the item that was clicked.
250 void onItemClick(AdapterView<?> parent, View view, int position, long id);
254 * Register a callback to be invoked when an item in this AdapterView has
257 * @param listener The callback that will be invoked.
259 public void setOnItemClickListener(OnItemClickListener listener) {
260 mOnItemClickListener = listener;
264 * @return The callback to be invoked with an item in this AdapterView has
265 * been clicked, or null id no callback has been set.
267 public final OnItemClickListener getOnItemClickListener() {
268 return mOnItemClickListener;
272 * Call the OnItemClickListener, if it is defined.
274 * @param view The view within the AdapterView that was clicked.
275 * @param position The position of the view in the adapter.
276 * @param id The row id of the item that was clicked.
277 * @return True if there was an assigned OnItemClickListener that was
278 * called, false otherwise is returned.
280 public boolean performItemClick(View view, int position, long id) {
281 if (mOnItemClickListener != null) {
282 playSoundEffect(SoundEffectConstants.CLICK);
284 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
286 mOnItemClickListener.onItemClick(this, view, position, id);
294 * Interface definition for a callback to be invoked when an item in this
295 * view has been clicked and held.
297 public interface OnItemLongClickListener {
299 * Callback method to be invoked when an item in this view has been
302 * Implementers can call getItemAtPosition(position) if they need to access
303 * the data associated with the selected item.
305 * @param parent The AbsListView where the click happened
306 * @param view The view within the AbsListView that was clicked
307 * @param position The position of the view in the list
308 * @param id The row id of the item that was clicked
310 * @return true if the callback consumed the long click, false otherwise
312 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
317 * Register a callback to be invoked when an item in this AdapterView has
318 * been clicked and held
320 * @param listener The callback that will run
322 public void setOnItemLongClickListener(OnItemLongClickListener listener) {
323 if (!isLongClickable()) {
324 setLongClickable(true);
326 mOnItemLongClickListener = listener;
330 * @return The callback to be invoked with an item in this AdapterView has
331 * been clicked and held, or null id no callback as been set.
333 public final OnItemLongClickListener getOnItemLongClickListener() {
334 return mOnItemLongClickListener;
338 * Interface definition for a callback to be invoked when
339 * an item in this view has been selected.
341 public interface OnItemSelectedListener {
343 * <p>Callback method to be invoked when an item in this view has been
344 * selected. This callback is invoked only when the newly selected
345 * position is different from the previously selected position or if
346 * there was no selected item.</p>
348 * Impelmenters can call getItemAtPosition(position) if they need to access the
349 * data associated with the selected item.
351 * @param parent The AdapterView where the selection happened
352 * @param view The view within the AdapterView that was clicked
353 * @param position The position of the view in the adapter
354 * @param id The row id of the item that is selected
356 void onItemSelected(AdapterView<?> parent, View view, int position, long id);
359 * Callback method to be invoked when the selection disappears from this
360 * view. The selection can disappear for instance when touch is activated
361 * or when the adapter becomes empty.
363 * @param parent The AdapterView that now contains no selected item.
365 void onNothingSelected(AdapterView<?> parent);
370 * Register a callback to be invoked when an item in this AdapterView has
373 * @param listener The callback that will run
375 public void setOnItemSelectedListener(OnItemSelectedListener listener) {
376 mOnItemSelectedListener = listener;
379 public final OnItemSelectedListener getOnItemSelectedListener() {
380 return mOnItemSelectedListener;
384 * Extra menu information provided to the
385 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
386 * callback when a context menu is brought up for this AdapterView.
389 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
391 public AdapterContextMenuInfo(View targetView, int position, long id) {
392 this.targetView = targetView;
393 this.position = position;
398 * The child view for which the context menu is being displayed. This
399 * will be one of the children of this AdapterView.
401 public View targetView;
404 * The position in the adapter for which the context menu is being
410 * The row id of the item for which the context menu is being displayed.
416 * Returns the adapter currently associated with this widget.
418 * @return The adapter used to provide this view's content.
420 public abstract T getAdapter();
423 * Sets the adapter that provides the data and the views to represent the data
426 * @param adapter The adapter to use to create this view's content.
428 public abstract void setAdapter(T adapter);
431 * This method is not supported and throws an UnsupportedOperationException when called.
433 * @param child Ignored.
435 * @throws UnsupportedOperationException Every time this method is invoked.
438 public void addView(View child) {
439 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
443 * This method is not supported and throws an UnsupportedOperationException when called.
445 * @param child Ignored.
446 * @param index Ignored.
448 * @throws UnsupportedOperationException Every time this method is invoked.
451 public void addView(View child, int index) {
452 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
456 * This method is not supported and throws an UnsupportedOperationException when called.
458 * @param child Ignored.
459 * @param params Ignored.
461 * @throws UnsupportedOperationException Every time this method is invoked.
464 public void addView(View child, LayoutParams params) {
465 throw new UnsupportedOperationException("addView(View, LayoutParams) "
466 + "is not supported in AdapterView");
470 * This method is not supported and throws an UnsupportedOperationException when called.
472 * @param child Ignored.
473 * @param index Ignored.
474 * @param params Ignored.
476 * @throws UnsupportedOperationException Every time this method is invoked.
479 public void addView(View child, int index, LayoutParams params) {
480 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
481 + "is not supported in AdapterView");
485 * This method is not supported and throws an UnsupportedOperationException when called.
487 * @param child Ignored.
489 * @throws UnsupportedOperationException Every time this method is invoked.
492 public void removeView(View child) {
493 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
497 * This method is not supported and throws an UnsupportedOperationException when called.
499 * @param index Ignored.
501 * @throws UnsupportedOperationException Every time this method is invoked.
504 public void removeViewAt(int index) {
505 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
509 * This method is not supported and throws an UnsupportedOperationException when called.
511 * @throws UnsupportedOperationException Every time this method is invoked.
514 public void removeAllViews() {
515 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
519 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
520 mLayoutHeight = getHeight();
524 * Return the position of the currently selected item within the adapter's data set
526 * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
528 @ViewDebug.CapturedViewProperty
529 public int getSelectedItemPosition() {
530 return mNextSelectedPosition;
534 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
535 * if nothing is selected.
537 @ViewDebug.CapturedViewProperty
538 public long getSelectedItemId() {
539 return mNextSelectedRowId;
543 * @return The view corresponding to the currently selected item, or null
544 * if nothing is selected
546 public abstract View getSelectedView();
549 * @return The data corresponding to the currently selected item, or
550 * null if there is nothing selected.
552 public Object getSelectedItem() {
553 T adapter = getAdapter();
554 int selection = getSelectedItemPosition();
555 if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
556 return adapter.getItem(selection);
563 * @return The number of items owned by the Adapter associated with this
564 * AdapterView. (This is the number of data items, which may be
565 * larger than the number of visible views.)
567 @ViewDebug.CapturedViewProperty
568 public int getCount() {
573 * Get the position within the adapter's data set for the view, where view is a an adapter item
574 * or a descendant of an adapter item.
576 * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
577 * AdapterView at the time of the call.
578 * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
579 * if the view does not correspond to a list item (or it is not currently visible).
581 public int getPositionForView(View view) {
582 View listItem = view;
585 while (!(v = (View) listItem.getParent()).equals(this)) {
588 } catch (ClassCastException e) {
589 // We made it up to the window without find this list view
590 return INVALID_POSITION;
593 // Search the children for the list item
594 final int childCount = getChildCount();
595 for (int i = 0; i < childCount; i++) {
596 if (getChildAt(i).equals(listItem)) {
597 return mFirstPosition + i;
602 return INVALID_POSITION;
606 * Returns the position within the adapter's data set for the first item
607 * displayed on screen.
609 * @return The position within the adapter's data set
611 public int getFirstVisiblePosition() {
612 return mFirstPosition;
616 * Returns the position within the adapter's data set for the last item
617 * displayed on screen.
619 * @return The position within the adapter's data set
621 public int getLastVisiblePosition() {
622 return mFirstPosition + getChildCount() - 1;
626 * Sets the currently selected item. To support accessibility subclasses that
627 * override this method must invoke the overriden super method first.
629 * @param position Index (starting at 0) of the data item to be selected.
631 public abstract void setSelection(int position);
634 * Sets the view to show if the adapter is empty
636 @android.view.RemotableViewMethod
637 public void setEmptyView(View emptyView) {
638 mEmptyView = emptyView;
640 final T adapter = getAdapter();
641 final boolean empty = ((adapter == null) || adapter.isEmpty());
642 updateEmptyStatus(empty);
646 * When the current adapter is empty, the AdapterView can display a special view
647 * call the empty view. The empty view is used to provide feedback to the user
648 * that no data is available in this AdapterView.
650 * @return The view to show if the adapter is empty.
652 public View getEmptyView() {
657 * Indicates whether this view is in filter mode. Filter mode can for instance
658 * be enabled by a user when typing on the keyboard.
660 * @return True if the view is in filter mode, false otherwise.
662 boolean isInFilterMode() {
667 public void setFocusable(boolean focusable) {
668 final T adapter = getAdapter();
669 final boolean empty = adapter == null || adapter.getCount() == 0;
671 mDesiredFocusableState = focusable;
673 mDesiredFocusableInTouchModeState = false;
676 super.setFocusable(focusable && (!empty || isInFilterMode()));
680 public void setFocusableInTouchMode(boolean focusable) {
681 final T adapter = getAdapter();
682 final boolean empty = adapter == null || adapter.getCount() == 0;
684 mDesiredFocusableInTouchModeState = focusable;
686 mDesiredFocusableState = true;
689 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
693 final T adapter = getAdapter();
694 final boolean empty = adapter == null || adapter.getCount() == 0;
695 final boolean focusable = !empty || isInFilterMode();
696 // The order in which we set focusable in touch mode/focusable may matter
697 // for the client, see View.setFocusableInTouchMode() comments for more
699 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
700 super.setFocusable(focusable && mDesiredFocusableState);
701 if (mEmptyView != null) {
702 updateEmptyStatus((adapter == null) || adapter.isEmpty());
707 * Update the status of the list based on the empty parameter. If empty is true and
708 * we have an empty view, display it. In all the other cases, make sure that the listview
709 * is VISIBLE and that the empty view is GONE (if it's not null).
711 private void updateEmptyStatus(boolean empty) {
712 if (isInFilterMode()) {
717 if (mEmptyView != null) {
718 mEmptyView.setVisibility(View.VISIBLE);
719 setVisibility(View.GONE);
721 // If the caller just removed our empty view, make sure the list view is visible
722 setVisibility(View.VISIBLE);
725 // We are now GONE, so pending layouts will not be dispatched.
726 // Force one here to make sure that the state of the list matches
727 // the state of the adapter.
729 this.onLayout(false, mLeft, mTop, mRight, mBottom);
732 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
733 setVisibility(View.VISIBLE);
738 * Gets the data associated with the specified position in the list.
740 * @param position Which data to get
741 * @return The data associated with the specified position in the list
743 public Object getItemAtPosition(int position) {
744 T adapter = getAdapter();
745 return (adapter == null || position < 0) ? null : adapter.getItem(position);
748 public long getItemIdAtPosition(int position) {
749 T adapter = getAdapter();
750 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
754 public void setOnClickListener(OnClickListener l) {
755 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
756 + "You probably want setOnItemClickListener instead");
760 * Override to prevent freezing of any views created by the adapter.
763 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
764 dispatchFreezeSelfOnly(container);
768 * Override to prevent thawing of any views created by the adapter.
771 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
772 dispatchThawSelfOnly(container);
775 class AdapterDataSetObserver extends DataSetObserver {
777 private Parcelable mInstanceState = null;
780 public void onChanged() {
782 mOldItemCount = mItemCount;
783 mItemCount = getAdapter().getCount();
785 // Detect the case where a cursor that was previously invalidated has
786 // been repopulated with new data.
787 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
788 && mOldItemCount == 0 && mItemCount > 0) {
789 AdapterView.this.onRestoreInstanceState(mInstanceState);
790 mInstanceState = null;
799 public void onInvalidated() {
802 if (AdapterView.this.getAdapter().hasStableIds()) {
803 // Remember the current state for the case where our hosting activity is being
804 // stopped and later restarted
805 mInstanceState = AdapterView.this.onSaveInstanceState();
808 // Data is invalid so we should reset our state
809 mOldItemCount = mItemCount;
811 mSelectedPosition = INVALID_POSITION;
812 mSelectedRowId = INVALID_ROW_ID;
813 mNextSelectedPosition = INVALID_POSITION;
814 mNextSelectedRowId = INVALID_ROW_ID;
821 public void clearSavedState() {
822 mInstanceState = null;
827 protected void onDetachedFromWindow() {
828 super.onDetachedFromWindow();
829 removeCallbacks(mSelectionNotifier);
832 private class SelectionNotifier implements Runnable {
835 // Data has changed between when this SelectionNotifier
836 // was posted and now. We need to wait until the AdapterView
837 // has been synched to the new data.
838 if (getAdapter() != null) {
847 void selectionChanged() {
848 if (mOnItemSelectedListener != null) {
849 if (mInLayout || mBlockLayoutRequests) {
850 // If we are in a layout traversal, defer notification
851 // by posting. This ensures that the view tree is
852 // in a consistent state and is able to accomodate
853 // new layout or invalidate requests.
854 if (mSelectionNotifier == null) {
855 mSelectionNotifier = new SelectionNotifier();
857 post(mSelectionNotifier);
863 // we fire selection events here not in View
864 if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
865 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
869 private void fireOnSelected() {
870 if (mOnItemSelectedListener == null)
873 int selection = this.getSelectedItemPosition();
874 if (selection >= 0) {
875 View v = getSelectedView();
876 mOnItemSelectedListener.onItemSelected(this, v, selection,
877 getAdapter().getItemId(selection));
879 mOnItemSelectedListener.onNothingSelected(this);
884 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
885 View selectedView = getSelectedView();
886 if (selectedView != null && selectedView.getVisibility() == VISIBLE
887 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
894 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
895 if (super.onRequestSendAccessibilityEvent(child, event)) {
896 // Add a record for ourselves as well.
897 AccessibilityEvent record = AccessibilityEvent.obtain();
898 onInitializeAccessibilityEvent(record);
899 // Populate with the text of the requesting child.
900 child.dispatchPopulateAccessibilityEvent(record);
901 event.appendRecord(record);
908 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
909 super.onInitializeAccessibilityNodeInfo(info);
910 info.setScrollable(isScrollableForAccessibility());
911 View selectedView = getSelectedView();
912 if (selectedView != null) {
913 info.setEnabled(selectedView.isEnabled());
918 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
919 super.onInitializeAccessibilityEvent(event);
920 event.setScrollable(isScrollableForAccessibility());
921 View selectedView = getSelectedView();
922 if (selectedView != null) {
923 event.setEnabled(selectedView.isEnabled());
925 event.setCurrentItemIndex(getSelectedItemPosition());
926 event.setFromIndex(getFirstVisiblePosition());
927 event.setToIndex(getLastVisiblePosition());
928 event.setItemCount(getAdapter().getCount());
931 private boolean isScrollableForAccessibility() {
932 T adapter = getAdapter();
933 if (adapter != null) {
934 final int itemCount = adapter.getCount();
936 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
942 protected boolean canAnimate() {
943 return super.canAnimate() && mItemCount > 0;
946 void handleDataChanged() {
947 final int count = mItemCount;
948 boolean found = false;
954 // Find the row we are supposed to sync to
956 // Update this first, since setNextSelectedPositionInt inspects
960 // See if we can find a position in the new data with the same
961 // id as the old selection
962 newPos = findSyncPosition();
964 // Verify that new selection is selectable
965 int selectablePos = lookForSelectablePosition(newPos, true);
966 if (selectablePos == newPos) {
967 // Same row id is selected
968 setNextSelectedPositionInt(newPos);
974 // Try to use the same position if we can't find matching data
975 newPos = getSelectedItemPosition();
977 // Pin position to the available range
978 if (newPos >= count) {
985 // Make sure we select something selectable -- first look down
986 int selectablePos = lookForSelectablePosition(newPos, true);
987 if (selectablePos < 0) {
988 // Looking down didn't work -- try looking up
989 selectablePos = lookForSelectablePosition(newPos, false);
991 if (selectablePos >= 0) {
992 setNextSelectedPositionInt(selectablePos);
993 checkSelectionChanged();
999 // Nothing is selected
1000 mSelectedPosition = INVALID_POSITION;
1001 mSelectedRowId = INVALID_ROW_ID;
1002 mNextSelectedPosition = INVALID_POSITION;
1003 mNextSelectedRowId = INVALID_ROW_ID;
1005 checkSelectionChanged();
1009 void checkSelectionChanged() {
1010 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1012 mOldSelectedPosition = mSelectedPosition;
1013 mOldSelectedRowId = mSelectedRowId;
1018 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1019 * and then alternates between moving up and moving down until 1) we find the right position, or
1020 * 2) we run out of time, or 3) we have looked at every position
1022 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1025 int findSyncPosition() {
1026 int count = mItemCount;
1029 return INVALID_POSITION;
1032 long idToMatch = mSyncRowId;
1033 int seed = mSyncPosition;
1035 // If there isn't a selection don't hunt for it
1036 if (idToMatch == INVALID_ROW_ID) {
1037 return INVALID_POSITION;
1040 // Pin seed to reasonable values
1041 seed = Math.max(0, seed);
1042 seed = Math.min(count - 1, seed);
1044 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1048 // first position scanned so far
1051 // last position scanned so far
1054 // True if we should move down on the next iteration
1055 boolean next = false;
1057 // True when we have looked at the first item in the data
1060 // True when we have looked at the last item in the data
1063 // Get the item ID locally (instead of getItemIdAtPosition), so
1064 // we need the adapter
1065 T adapter = getAdapter();
1066 if (adapter == null) {
1067 return INVALID_POSITION;
1070 while (SystemClock.uptimeMillis() <= endTime) {
1071 rowId = adapter.getItemId(seed);
1072 if (rowId == idToMatch) {
1077 hitLast = last == count - 1;
1078 hitFirst = first == 0;
1080 if (hitLast && hitFirst) {
1081 // Looked at everything
1085 if (hitFirst || (next && !hitLast)) {
1086 // Either we hit the top, or we are trying to move down
1089 // Try going up next time
1091 } else if (hitLast || (!next && !hitFirst)) {
1092 // Either we hit the bottom, or we are trying to move up
1095 // Try going down next time
1101 return INVALID_POSITION;
1105 * Find a position that can be selected (i.e., is not a separator).
1107 * @param position The starting position to look at.
1108 * @param lookDown Whether to look down for other positions.
1109 * @return The next selectable position starting at position and then searching either up or
1110 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1112 int lookForSelectablePosition(int position, boolean lookDown) {
1117 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1118 * @param position Our current position
1120 void setSelectedPositionInt(int position) {
1121 mSelectedPosition = position;
1122 mSelectedRowId = getItemIdAtPosition(position);
1126 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1127 * @param position Intended value for mSelectedPosition the next time we go
1130 void setNextSelectedPositionInt(int position) {
1131 mNextSelectedPosition = position;
1132 mNextSelectedRowId = getItemIdAtPosition(position);
1133 // If we are trying to sync to the selection, update that too
1134 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1135 mSyncPosition = position;
1136 mSyncRowId = mNextSelectedRowId;
1141 * Remember enough information to restore the screen state when the data has
1145 void rememberSyncState() {
1146 if (getChildCount() > 0) {
1148 mSyncHeight = mLayoutHeight;
1149 if (mSelectedPosition >= 0) {
1150 // Sync the selection state
1151 View v = getChildAt(mSelectedPosition - mFirstPosition);
1152 mSyncRowId = mNextSelectedRowId;
1153 mSyncPosition = mNextSelectedPosition;
1155 mSpecificTop = v.getTop();
1157 mSyncMode = SYNC_SELECTED_POSITION;
1159 // Sync the based on the offset of the first view
1160 View v = getChildAt(0);
1161 T adapter = getAdapter();
1162 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1163 mSyncRowId = adapter.getItemId(mFirstPosition);
1167 mSyncPosition = mFirstPosition;
1169 mSpecificTop = v.getTop();
1171 mSyncMode = SYNC_FIRST_POSITION;