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 com
.actionbarsherlock
.internal
.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
.SoundEffectConstants
;
27 import android
.view
.View
;
28 import android
.view
.ViewDebug
;
29 import android
.view
.ViewGroup
;
30 import android
.view
.accessibility
.AccessibilityEvent
;
31 import android
.view
.accessibility
.AccessibilityNodeInfo
;
32 import android
.widget
.Adapter
;
33 import android
.widget
.AdapterView
.OnItemClickListener
;
34 import android
.widget
.ListView
;
38 * An AdapterView is a view whose children are determined by an {@link Adapter}.
41 * See {@link ListView}, {@link GridView}, {@link Spinner} and
42 * {@link Gallery} for commonly used subclasses of AdapterView.
44 * <div class="special reference">
45 * <h3>Developer Guides</h3>
46 * <p>For more information about using AdapterView, read the
47 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
48 * developer guide.</p></div>
50 public abstract class IcsAdapterView
<T
extends Adapter
> extends ViewGroup
{
53 * The item view type returned by {@link Adapter#getItemViewType(int)} when
54 * the adapter does not want the item's view recycled.
56 public static final int ITEM_VIEW_TYPE_IGNORE
= -1;
59 * The item view type returned by {@link Adapter#getItemViewType(int)} when
60 * the item is a header or footer.
62 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER
= -2;
65 * The position of the first child displayed
67 @ViewDebug.ExportedProperty(category
= "scrolling")
68 int mFirstPosition
= 0;
71 * The offset in pixels from the top of the AdapterView to the top
72 * of the view to select during the next layout.
77 * Position from which to start looking for mSyncRowId
82 * Row id to look for when data has changed
84 long mSyncRowId
= INVALID_ROW_ID
;
87 * Height of the view when mSyncPosition and mSyncRowId where set
92 * True if we need to sync to mSyncRowId
94 boolean mNeedSync
= false
;
97 * Indicates whether to sync based on the selection or position. Possible
98 * values are {@link #SYNC_SELECTED_POSITION} or
99 * {@link #SYNC_FIRST_POSITION}.
104 * Our height after the last layout
106 private int mLayoutHeight
;
109 * Sync based on the selected child
111 static final int SYNC_SELECTED_POSITION
= 0;
114 * Sync based on the first child displayed
116 static final int SYNC_FIRST_POSITION
= 1;
119 * Maximum amount of time to spend in {@link #findSyncPosition()}
121 static final int SYNC_MAX_DURATION_MILLIS
= 100;
124 * Indicates that this view is currently being laid out.
126 boolean mInLayout
= false
;
129 * The listener that receives notifications when an item is selected.
131 OnItemSelectedListener mOnItemSelectedListener
;
134 * The listener that receives notifications when an item is clicked.
136 OnItemClickListener mOnItemClickListener
;
139 * The listener that receives notifications when an item is long clicked.
141 OnItemLongClickListener mOnItemLongClickListener
;
144 * True if the data has changed since the last layout
146 boolean mDataChanged
;
149 * The position within the adapter's data set of the item to select
150 * during the next layout.
152 @ViewDebug.ExportedProperty(category
= "list")
153 int mNextSelectedPosition
= INVALID_POSITION
;
156 * The item id of the item to select during the next layout.
158 long mNextSelectedRowId
= INVALID_ROW_ID
;
161 * The position within the adapter's data set of the currently selected item.
163 @ViewDebug.ExportedProperty(category
= "list")
164 int mSelectedPosition
= INVALID_POSITION
;
167 * The item id of the currently selected item.
169 long mSelectedRowId
= INVALID_ROW_ID
;
172 * View to show if there are no items to show.
174 private View mEmptyView
;
177 * The number of items in the current adapter.
179 @ViewDebug.ExportedProperty(category
= "list")
183 * The number of items in the adapter before a data changed event occurred.
188 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
189 * number of items in the current adapter.
191 public static final int INVALID_POSITION
= -1;
194 * Represents an empty or invalid row id
196 public static final long INVALID_ROW_ID
= Long
.MIN_VALUE
;
199 * The last selected position we used when notifying
201 int mOldSelectedPosition
= INVALID_POSITION
;
204 * The id of the last selected position we used when notifying
206 long mOldSelectedRowId
= INVALID_ROW_ID
;
209 * Indicates what focusable state is requested when calling setFocusable().
210 * In addition to this, this view has other criteria for actually
211 * determining the focusable state (such as whether its empty or the text
214 * @see #setFocusable(boolean)
217 private boolean mDesiredFocusableState
;
218 private boolean mDesiredFocusableInTouchModeState
;
220 private SelectionNotifier mSelectionNotifier
;
222 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
223 * This is used to layout the children during a layout pass.
225 boolean mBlockLayoutRequests
= false
;
227 public IcsAdapterView(Context context
) {
231 public IcsAdapterView(Context context
, AttributeSet attrs
) {
232 super(context
, attrs
);
235 public IcsAdapterView(Context context
, AttributeSet attrs
, int defStyle
) {
236 super(context
, attrs
, defStyle
);
240 * Register a callback to be invoked when an item in this AdapterView has
243 * @param listener The callback that will be invoked.
245 public void setOnItemClickListener(OnItemClickListener listener
) {
246 mOnItemClickListener
= listener
;
250 * @return The callback to be invoked with an item in this AdapterView has
251 * been clicked, or null id no callback has been set.
253 public final OnItemClickListener
getOnItemClickListener() {
254 return mOnItemClickListener
;
258 * Call the OnItemClickListener, if it is defined.
260 * @param view The view within the AdapterView that was clicked.
261 * @param position The position of the view in the adapter.
262 * @param id The row id of the item that was clicked.
263 * @return True if there was an assigned OnItemClickListener that was
264 * called, false otherwise is returned.
266 public boolean performItemClick(View view
, int position
, long id
) {
267 if (mOnItemClickListener
!= null
) {
268 playSoundEffect(SoundEffectConstants
.CLICK
);
270 view
.sendAccessibilityEvent(AccessibilityEvent
.TYPE_VIEW_CLICKED
);
272 mOnItemClickListener
.onItemClick(/*this*/null
, view
, position
, id
);
280 * Interface definition for a callback to be invoked when an item in this
281 * view has been clicked and held.
283 public interface OnItemLongClickListener
{
285 * Callback method to be invoked when an item in this view has been
288 * Implementers can call getItemAtPosition(position) if they need to access
289 * the data associated with the selected item.
291 * @param parent The AbsListView where the click happened
292 * @param view The view within the AbsListView that was clicked
293 * @param position The position of the view in the list
294 * @param id The row id of the item that was clicked
296 * @return true if the callback consumed the long click, false otherwise
298 boolean onItemLongClick(IcsAdapterView
<?
> parent
, View view
, int position
, long id
);
303 * Register a callback to be invoked when an item in this AdapterView has
304 * been clicked and held
306 * @param listener The callback that will run
308 public void setOnItemLongClickListener(OnItemLongClickListener listener
) {
309 if (!isLongClickable()) {
310 setLongClickable(true
);
312 mOnItemLongClickListener
= listener
;
316 * @return The callback to be invoked with an item in this AdapterView has
317 * been clicked and held, or null id no callback as been set.
319 public final OnItemLongClickListener
getOnItemLongClickListener() {
320 return mOnItemLongClickListener
;
324 * Interface definition for a callback to be invoked when
325 * an item in this view has been selected.
327 public interface OnItemSelectedListener
{
329 * <p>Callback method to be invoked when an item in this view has been
330 * selected. This callback is invoked only when the newly selected
331 * position is different from the previously selected position or if
332 * there was no selected item.</p>
334 * Impelmenters can call getItemAtPosition(position) if they need to access the
335 * data associated with the selected item.
337 * @param parent The AdapterView where the selection happened
338 * @param view The view within the AdapterView that was clicked
339 * @param position The position of the view in the adapter
340 * @param id The row id of the item that is selected
342 void onItemSelected(IcsAdapterView
<?
> parent
, View view
, int position
, long id
);
345 * Callback method to be invoked when the selection disappears from this
346 * view. The selection can disappear for instance when touch is activated
347 * or when the adapter becomes empty.
349 * @param parent The AdapterView that now contains no selected item.
351 void onNothingSelected(IcsAdapterView
<?
> parent
);
356 * Register a callback to be invoked when an item in this AdapterView has
359 * @param listener The callback that will run
361 public void setOnItemSelectedListener(OnItemSelectedListener listener
) {
362 mOnItemSelectedListener
= listener
;
365 public final OnItemSelectedListener
getOnItemSelectedListener() {
366 return mOnItemSelectedListener
;
370 * Extra menu information provided to the
371 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
372 * callback when a context menu is brought up for this AdapterView.
375 public static class AdapterContextMenuInfo
implements ContextMenu
.ContextMenuInfo
{
377 public AdapterContextMenuInfo(View targetView
, int position
, long id
) {
378 this.targetView
= targetView
;
379 this.position
= position
;
384 * The child view for which the context menu is being displayed. This
385 * will be one of the children of this AdapterView.
387 public View targetView
;
390 * The position in the adapter for which the context menu is being
396 * The row id of the item for which the context menu is being displayed.
402 * Returns the adapter currently associated with this widget.
404 * @return The adapter used to provide this view's content.
406 public abstract T
getAdapter();
409 * Sets the adapter that provides the data and the views to represent the data
412 * @param adapter The adapter to use to create this view's content.
414 public abstract void setAdapter(T adapter
);
417 * This method is not supported and throws an UnsupportedOperationException when called.
419 * @param child Ignored.
421 * @throws UnsupportedOperationException Every time this method is invoked.
424 public void addView(View child
) {
425 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
429 * This method is not supported and throws an UnsupportedOperationException when called.
431 * @param child Ignored.
432 * @param index Ignored.
434 * @throws UnsupportedOperationException Every time this method is invoked.
437 public void addView(View child
, int index
) {
438 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
442 * This method is not supported and throws an UnsupportedOperationException when called.
444 * @param child Ignored.
445 * @param params Ignored.
447 * @throws UnsupportedOperationException Every time this method is invoked.
450 public void addView(View child
, LayoutParams params
) {
451 throw new UnsupportedOperationException("addView(View, LayoutParams) "
452 + "is not supported in AdapterView");
456 * This method is not supported and throws an UnsupportedOperationException when called.
458 * @param child Ignored.
459 * @param index Ignored.
460 * @param params Ignored.
462 * @throws UnsupportedOperationException Every time this method is invoked.
465 public void addView(View child
, int index
, LayoutParams params
) {
466 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
467 + "is not supported in AdapterView");
471 * This method is not supported and throws an UnsupportedOperationException when called.
473 * @param child Ignored.
475 * @throws UnsupportedOperationException Every time this method is invoked.
478 public void removeView(View child
) {
479 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
483 * This method is not supported and throws an UnsupportedOperationException when called.
485 * @param index Ignored.
487 * @throws UnsupportedOperationException Every time this method is invoked.
490 public void removeViewAt(int index
) {
491 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
495 * This method is not supported and throws an UnsupportedOperationException when called.
497 * @throws UnsupportedOperationException Every time this method is invoked.
500 public void removeAllViews() {
501 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
505 protected void onLayout(boolean changed
, int left
, int top
, int right
, int bottom
) {
506 mLayoutHeight
= getHeight();
510 * Return the position of the currently selected item within the adapter's data set
512 * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
514 @ViewDebug.CapturedViewProperty
515 public int getSelectedItemPosition() {
516 return mNextSelectedPosition
;
520 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
521 * if nothing is selected.
523 @ViewDebug.CapturedViewProperty
524 public long getSelectedItemId() {
525 return mNextSelectedRowId
;
529 * @return The view corresponding to the currently selected item, or null
530 * if nothing is selected
532 public abstract View
getSelectedView();
535 * @return The data corresponding to the currently selected item, or
536 * null if there is nothing selected.
538 public Object
getSelectedItem() {
539 T adapter
= getAdapter();
540 int selection
= getSelectedItemPosition();
541 if (adapter
!= null
&& adapter
.getCount() > 0 && selection
>= 0) {
542 return adapter
.getItem(selection
);
549 * @return The number of items owned by the Adapter associated with this
550 * AdapterView. (This is the number of data items, which may be
551 * larger than the number of visible views.)
553 @ViewDebug.CapturedViewProperty
554 public int getCount() {
559 * Get the position within the adapter's data set for the view, where view is a an adapter item
560 * or a descendant of an adapter item.
562 * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
563 * AdapterView at the time of the call.
564 * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
565 * if the view does not correspond to a list item (or it is not currently visible).
567 public int getPositionForView(View view
) {
568 View listItem
= view
;
571 while (!(v
= (View
) listItem
.getParent()).equals(this)) {
574 } catch (ClassCastException e
) {
575 // We made it up to the window without find this list view
576 return INVALID_POSITION
;
579 // Search the children for the list item
580 final int childCount
= getChildCount();
581 for (int i
= 0; i
< childCount
; i
++) {
582 if (getChildAt(i
).equals(listItem
)) {
583 return mFirstPosition
+ i
;
588 return INVALID_POSITION
;
592 * Returns the position within the adapter's data set for the first item
593 * displayed on screen.
595 * @return The position within the adapter's data set
597 public int getFirstVisiblePosition() {
598 return mFirstPosition
;
602 * Returns the position within the adapter's data set for the last item
603 * displayed on screen.
605 * @return The position within the adapter's data set
607 public int getLastVisiblePosition() {
608 return mFirstPosition
+ getChildCount() - 1;
612 * Sets the currently selected item. To support accessibility subclasses that
613 * override this method must invoke the overriden super method first.
615 * @param position Index (starting at 0) of the data item to be selected.
617 public abstract void setSelection(int position
);
620 * Sets the view to show if the adapter is empty
622 public void setEmptyView(View emptyView
) {
623 mEmptyView
= emptyView
;
625 final T adapter
= getAdapter();
626 final boolean empty
= ((adapter
== null
) || adapter
.isEmpty());
627 updateEmptyStatus(empty
);
631 * When the current adapter is empty, the AdapterView can display a special view
632 * call the empty view. The empty view is used to provide feedback to the user
633 * that no data is available in this AdapterView.
635 * @return The view to show if the adapter is empty.
637 public View
getEmptyView() {
642 * Indicates whether this view is in filter mode. Filter mode can for instance
643 * be enabled by a user when typing on the keyboard.
645 * @return True if the view is in filter mode, false otherwise.
647 boolean isInFilterMode() {
652 public void setFocusable(boolean focusable
) {
653 final T adapter
= getAdapter();
654 final boolean empty
= adapter
== null
|| adapter
.getCount() == 0;
656 mDesiredFocusableState
= focusable
;
658 mDesiredFocusableInTouchModeState
= false
;
661 super.setFocusable(focusable
&& (!empty
|| isInFilterMode()));
665 public void setFocusableInTouchMode(boolean focusable
) {
666 final T adapter
= getAdapter();
667 final boolean empty
= adapter
== null
|| adapter
.getCount() == 0;
669 mDesiredFocusableInTouchModeState
= focusable
;
671 mDesiredFocusableState
= true
;
674 super.setFocusableInTouchMode(focusable
&& (!empty
|| isInFilterMode()));
678 final T adapter
= getAdapter();
679 final boolean empty
= adapter
== null
|| adapter
.getCount() == 0;
680 final boolean focusable
= !empty
|| isInFilterMode();
681 // The order in which we set focusable in touch mode/focusable may matter
682 // for the client, see View.setFocusableInTouchMode() comments for more
684 super.setFocusableInTouchMode(focusable
&& mDesiredFocusableInTouchModeState
);
685 super.setFocusable(focusable
&& mDesiredFocusableState
);
686 if (mEmptyView
!= null
) {
687 updateEmptyStatus((adapter
== null
) || adapter
.isEmpty());
692 * Update the status of the list based on the empty parameter. If empty is true and
693 * we have an empty view, display it. In all the other cases, make sure that the listview
694 * is VISIBLE and that the empty view is GONE (if it's not null).
696 private void updateEmptyStatus(boolean empty
) {
697 if (isInFilterMode()) {
702 if (mEmptyView
!= null
) {
703 mEmptyView
.setVisibility(View
.VISIBLE
);
704 setVisibility(View
.GONE
);
706 // If the caller just removed our empty view, make sure the list view is visible
707 setVisibility(View
.VISIBLE
);
710 // We are now GONE, so pending layouts will not be dispatched.
711 // Force one here to make sure that the state of the list matches
712 // the state of the adapter.
714 this.onLayout(false
, getLeft(), getTop(), getRight(), getBottom());
717 if (mEmptyView
!= null
) mEmptyView
.setVisibility(View
.GONE
);
718 setVisibility(View
.VISIBLE
);
723 * Gets the data associated with the specified position in the list.
725 * @param position Which data to get
726 * @return The data associated with the specified position in the list
728 public Object
getItemAtPosition(int position
) {
729 T adapter
= getAdapter();
730 return (adapter
== null
|| position
< 0) ? null
: adapter
.getItem(position
);
733 public long getItemIdAtPosition(int position
) {
734 T adapter
= getAdapter();
735 return (adapter
== null
|| position
< 0) ? INVALID_ROW_ID
: adapter
.getItemId(position
);
739 public void setOnClickListener(OnClickListener l
) {
740 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
741 + "You probably want setOnItemClickListener instead");
745 * Override to prevent freezing of any views created by the adapter.
748 protected void dispatchSaveInstanceState(SparseArray
<Parcelable
> container
) {
749 dispatchFreezeSelfOnly(container
);
753 * Override to prevent thawing of any views created by the adapter.
756 protected void dispatchRestoreInstanceState(SparseArray
<Parcelable
> container
) {
757 dispatchThawSelfOnly(container
);
760 class AdapterDataSetObserver
extends DataSetObserver
{
762 private Parcelable mInstanceState
= null
;
765 public void onChanged() {
767 mOldItemCount
= mItemCount
;
768 mItemCount
= getAdapter().getCount();
770 // Detect the case where a cursor that was previously invalidated has
771 // been repopulated with new data.
772 if (IcsAdapterView
.this.getAdapter().hasStableIds() && mInstanceState
!= null
773 && mOldItemCount
== 0 && mItemCount
> 0) {
774 IcsAdapterView
.this.onRestoreInstanceState(mInstanceState
);
775 mInstanceState
= null
;
784 public void onInvalidated() {
787 if (IcsAdapterView
.this.getAdapter().hasStableIds()) {
788 // Remember the current state for the case where our hosting activity is being
789 // stopped and later restarted
790 mInstanceState
= IcsAdapterView
.this.onSaveInstanceState();
793 // Data is invalid so we should reset our state
794 mOldItemCount
= mItemCount
;
796 mSelectedPosition
= INVALID_POSITION
;
797 mSelectedRowId
= INVALID_ROW_ID
;
798 mNextSelectedPosition
= INVALID_POSITION
;
799 mNextSelectedRowId
= INVALID_ROW_ID
;
806 public void clearSavedState() {
807 mInstanceState
= null
;
812 protected void onDetachedFromWindow() {
813 super.onDetachedFromWindow();
814 removeCallbacks(mSelectionNotifier
);
817 private class SelectionNotifier
implements Runnable
{
820 // Data has changed between when this SelectionNotifier
821 // was posted and now. We need to wait until the AdapterView
822 // has been synched to the new data.
823 if (getAdapter() != null
) {
832 void selectionChanged() {
833 if (mOnItemSelectedListener
!= null
) {
834 if (mInLayout
|| mBlockLayoutRequests
) {
835 // If we are in a layout traversal, defer notification
836 // by posting. This ensures that the view tree is
837 // in a consistent state and is able to accomodate
838 // new layout or invalidate requests.
839 if (mSelectionNotifier
== null
) {
840 mSelectionNotifier
= new SelectionNotifier();
842 post(mSelectionNotifier
);
848 // we fire selection events here not in View
849 if (mSelectedPosition
!= ListView
.INVALID_POSITION
&& isShown() && !isInTouchMode()) {
850 sendAccessibilityEvent(AccessibilityEvent
.TYPE_VIEW_SELECTED
);
854 private void fireOnSelected() {
855 if (mOnItemSelectedListener
== null
)
858 int selection
= this.getSelectedItemPosition();
859 if (selection
>= 0) {
860 View v
= getSelectedView();
861 mOnItemSelectedListener
.onItemSelected(this, v
, selection
,
862 getAdapter().getItemId(selection
));
864 mOnItemSelectedListener
.onNothingSelected(this);
869 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event
) {
870 View selectedView
= getSelectedView();
871 if (selectedView
!= null
&& selectedView
.getVisibility() == VISIBLE
872 && selectedView
.dispatchPopulateAccessibilityEvent(event
)) {
879 public boolean onRequestSendAccessibilityEvent(View child
, AccessibilityEvent event
) {
880 if (super.onRequestSendAccessibilityEvent(child
, event
)) {
881 // Add a record for ourselves as well.
882 AccessibilityEvent record
= AccessibilityEvent
.obtain();
883 onInitializeAccessibilityEvent(record
);
884 // Populate with the text of the requesting child.
885 child
.dispatchPopulateAccessibilityEvent(record
);
886 event
.appendRecord(record
);
893 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info
) {
894 super.onInitializeAccessibilityNodeInfo(info
);
895 info
.setScrollable(isScrollableForAccessibility());
896 View selectedView
= getSelectedView();
897 if (selectedView
!= null
) {
898 info
.setEnabled(selectedView
.isEnabled());
903 public void onInitializeAccessibilityEvent(AccessibilityEvent event
) {
904 super.onInitializeAccessibilityEvent(event
);
905 event
.setScrollable(isScrollableForAccessibility());
906 View selectedView
= getSelectedView();
907 if (selectedView
!= null
) {
908 event
.setEnabled(selectedView
.isEnabled());
910 event
.setCurrentItemIndex(getSelectedItemPosition());
911 event
.setFromIndex(getFirstVisiblePosition());
912 event
.setToIndex(getLastVisiblePosition());
913 event
.setItemCount(getCount());
916 private boolean isScrollableForAccessibility() {
917 T adapter
= getAdapter();
918 if (adapter
!= null
) {
919 final int itemCount
= adapter
.getCount();
921 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount
- 1);
927 protected boolean canAnimate() {
928 return super.canAnimate() && mItemCount
> 0;
931 void handleDataChanged() {
932 final int count
= mItemCount
;
933 boolean found
= false
;
939 // Find the row we are supposed to sync to
941 // Update this first, since setNextSelectedPositionInt inspects
945 // See if we can find a position in the new data with the same
946 // id as the old selection
947 newPos
= findSyncPosition();
949 // Verify that new selection is selectable
950 int selectablePos
= lookForSelectablePosition(newPos
, true
);
951 if (selectablePos
== newPos
) {
952 // Same row id is selected
953 setNextSelectedPositionInt(newPos
);
959 // Try to use the same position if we can't find matching data
960 newPos
= getSelectedItemPosition();
962 // Pin position to the available range
963 if (newPos
>= count
) {
970 // Make sure we select something selectable -- first look down
971 int selectablePos
= lookForSelectablePosition(newPos
, true
);
972 if (selectablePos
< 0) {
973 // Looking down didn't work -- try looking up
974 selectablePos
= lookForSelectablePosition(newPos
, false
);
976 if (selectablePos
>= 0) {
977 setNextSelectedPositionInt(selectablePos
);
978 checkSelectionChanged();
984 // Nothing is selected
985 mSelectedPosition
= INVALID_POSITION
;
986 mSelectedRowId
= INVALID_ROW_ID
;
987 mNextSelectedPosition
= INVALID_POSITION
;
988 mNextSelectedRowId
= INVALID_ROW_ID
;
990 checkSelectionChanged();
994 void checkSelectionChanged() {
995 if ((mSelectedPosition
!= mOldSelectedPosition
) || (mSelectedRowId
!= mOldSelectedRowId
)) {
997 mOldSelectedPosition
= mSelectedPosition
;
998 mOldSelectedRowId
= mSelectedRowId
;
1003 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1004 * and then alternates between moving up and moving down until 1) we find the right position, or
1005 * 2) we run out of time, or 3) we have looked at every position
1007 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1010 int findSyncPosition() {
1011 int count
= mItemCount
;
1014 return INVALID_POSITION
;
1017 long idToMatch
= mSyncRowId
;
1018 int seed
= mSyncPosition
;
1020 // If there isn't a selection don't hunt for it
1021 if (idToMatch
== INVALID_ROW_ID
) {
1022 return INVALID_POSITION
;
1025 // Pin seed to reasonable values
1026 seed
= Math
.max(0, seed
);
1027 seed
= Math
.min(count
- 1, seed
);
1029 long endTime
= SystemClock
.uptimeMillis() + SYNC_MAX_DURATION_MILLIS
;
1033 // first position scanned so far
1036 // last position scanned so far
1039 // True if we should move down on the next iteration
1040 boolean next
= false
;
1042 // True when we have looked at the first item in the data
1045 // True when we have looked at the last item in the data
1048 // Get the item ID locally (instead of getItemIdAtPosition), so
1049 // we need the adapter
1050 T adapter
= getAdapter();
1051 if (adapter
== null
) {
1052 return INVALID_POSITION
;
1055 while (SystemClock
.uptimeMillis() <= endTime
) {
1056 rowId
= adapter
.getItemId(seed
);
1057 if (rowId
== idToMatch
) {
1062 hitLast
= last
== count
- 1;
1063 hitFirst
= first
== 0;
1065 if (hitLast
&& hitFirst
) {
1066 // Looked at everything
1070 if (hitFirst
|| (next
&& !hitLast
)) {
1071 // Either we hit the top, or we are trying to move down
1074 // Try going up next time
1076 } else if (hitLast
|| (!next
&& !hitFirst
)) {
1077 // Either we hit the bottom, or we are trying to move up
1080 // Try going down next time
1086 return INVALID_POSITION
;
1090 * Find a position that can be selected (i.e., is not a separator).
1092 * @param position The starting position to look at.
1093 * @param lookDown Whether to look down for other positions.
1094 * @return The next selectable position starting at position and then searching either up or
1095 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1097 int lookForSelectablePosition(int position
, boolean lookDown
) {
1102 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1103 * @param position Our current position
1105 void setSelectedPositionInt(int position
) {
1106 mSelectedPosition
= position
;
1107 mSelectedRowId
= getItemIdAtPosition(position
);
1111 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1112 * @param position Intended value for mSelectedPosition the next time we go
1115 void setNextSelectedPositionInt(int position
) {
1116 mNextSelectedPosition
= position
;
1117 mNextSelectedRowId
= getItemIdAtPosition(position
);
1118 // If we are trying to sync to the selection, update that too
1119 if (mNeedSync
&& mSyncMode
== SYNC_SELECTED_POSITION
&& position
>= 0) {
1120 mSyncPosition
= position
;
1121 mSyncRowId
= mNextSelectedRowId
;
1126 * Remember enough information to restore the screen state when the data has
1130 void rememberSyncState() {
1131 if (getChildCount() > 0) {
1133 mSyncHeight
= mLayoutHeight
;
1134 if (mSelectedPosition
>= 0) {
1135 // Sync the selection state
1136 View v
= getChildAt(mSelectedPosition
- mFirstPosition
);
1137 mSyncRowId
= mNextSelectedRowId
;
1138 mSyncPosition
= mNextSelectedPosition
;
1140 mSpecificTop
= v
.getTop();
1142 mSyncMode
= SYNC_SELECTED_POSITION
;
1144 // Sync the based on the offset of the first view
1145 View v
= getChildAt(0);
1146 T adapter
= getAdapter();
1147 if (mFirstPosition
>= 0 && mFirstPosition
< adapter
.getCount()) {
1148 mSyncRowId
= adapter
.getItemId(mFirstPosition
);
1152 mSyncPosition
= mFirstPosition
;
1154 mSpecificTop
= v
.getTop();
1156 mSyncMode
= SYNC_FIRST_POSITION
;