blob: 6ed7ab8fde68b093821bbefda5ca9bbb4f093f95 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package android.widget;
18
Siva Velusamy94a6d152015-05-05 15:07:00 -070019import android.annotation.NonNull;
Scott Kennedyed2b5f82015-03-06 17:24:58 -080020import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
22import android.database.DataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.os.Parcelable;
24import android.os.SystemClock;
25import android.util.AttributeSet;
26import android.util.SparseArray;
27import android.view.ContextMenu;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070028import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.SoundEffectConstants;
svetoslavganov75986cf2009-05-14 22:28:01 -070030import android.view.View;
31import android.view.ViewDebug;
32import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070033import android.view.ViewHierarchyEncoder;
svetoslavganov75986cf2009-05-14 22:28:01 -070034import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -070035import android.view.accessibility.AccessibilityManager;
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -070036import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038/**
39 * An AdapterView is a view whose children are determined by an {@link Adapter}.
40 *
41 * <p>
42 * See {@link ListView}, {@link GridView}, {@link Spinner} and
43 * {@link Gallery} for commonly used subclasses of AdapterView.
Joe Fernandez61fd1e82011-10-26 13:39:11 -070044 *
45 * <div class="special reference">
46 * <h3>Developer Guides</h3>
47 * <p>For more information about using AdapterView, read the
48 * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
49 * developer guide.</p></div>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 */
51public abstract class AdapterView<T extends Adapter> extends ViewGroup {
52
53 /**
54 * The item view type returned by {@link Adapter#getItemViewType(int)} when
55 * the adapter does not want the item's view recycled.
56 */
57 public static final int ITEM_VIEW_TYPE_IGNORE = -1;
58
59 /**
60 * The item view type returned by {@link Adapter#getItemViewType(int)} when
61 * the item is a header or footer.
62 */
63 public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
64
65 /**
66 * The position of the first child displayed
67 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -070068 @ViewDebug.ExportedProperty(category = "scrolling")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 int mFirstPosition = 0;
70
71 /**
72 * The offset in pixels from the top of the AdapterView to the top
73 * of the view to select during the next layout.
74 */
75 int mSpecificTop;
76
77 /**
78 * Position from which to start looking for mSyncRowId
79 */
80 int mSyncPosition;
81
82 /**
83 * Row id to look for when data has changed
84 */
85 long mSyncRowId = INVALID_ROW_ID;
86
87 /**
88 * Height of the view when mSyncPosition and mSyncRowId where set
89 */
90 long mSyncHeight;
91
92 /**
93 * True if we need to sync to mSyncRowId
94 */
95 boolean mNeedSync = false;
96
97 /**
98 * Indicates whether to sync based on the selection or position. Possible
99 * values are {@link #SYNC_SELECTED_POSITION} or
100 * {@link #SYNC_FIRST_POSITION}.
101 */
102 int mSyncMode;
103
104 /**
105 * Our height after the last layout
106 */
107 private int mLayoutHeight;
108
109 /**
110 * Sync based on the selected child
111 */
112 static final int SYNC_SELECTED_POSITION = 0;
113
114 /**
115 * Sync based on the first child displayed
116 */
117 static final int SYNC_FIRST_POSITION = 1;
118
119 /**
120 * Maximum amount of time to spend in {@link #findSyncPosition()}
121 */
122 static final int SYNC_MAX_DURATION_MILLIS = 100;
123
124 /**
125 * Indicates that this view is currently being laid out.
126 */
127 boolean mInLayout = false;
128
129 /**
130 * The listener that receives notifications when an item is selected.
131 */
132 OnItemSelectedListener mOnItemSelectedListener;
133
134 /**
135 * The listener that receives notifications when an item is clicked.
136 */
137 OnItemClickListener mOnItemClickListener;
138
139 /**
140 * The listener that receives notifications when an item is long clicked.
141 */
142 OnItemLongClickListener mOnItemLongClickListener;
143
144 /**
145 * True if the data has changed since the last layout
146 */
147 boolean mDataChanged;
148
149 /**
150 * The position within the adapter's data set of the item to select
151 * during the next layout.
152 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700153 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 int mNextSelectedPosition = INVALID_POSITION;
155
156 /**
157 * The item id of the item to select during the next layout.
158 */
159 long mNextSelectedRowId = INVALID_ROW_ID;
160
161 /**
162 * The position within the adapter's data set of the currently selected item.
163 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700164 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 int mSelectedPosition = INVALID_POSITION;
166
167 /**
168 * The item id of the currently selected item.
169 */
170 long mSelectedRowId = INVALID_ROW_ID;
171
172 /**
173 * View to show if there are no items to show.
174 */
Mihai Predab6af5332009-04-28 14:21:57 +0200175 private View mEmptyView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176
177 /**
178 * The number of items in the current adapter.
179 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700180 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 int mItemCount;
182
183 /**
Chet Haase5e25c2c2010-09-16 11:15:56 -0700184 * The number of items in the adapter before a data changed event occurred.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 */
186 int mOldItemCount;
187
188 /**
189 * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
190 * number of items in the current adapter.
191 */
192 public static final int INVALID_POSITION = -1;
193
194 /**
195 * Represents an empty or invalid row id
196 */
197 public static final long INVALID_ROW_ID = Long.MIN_VALUE;
198
199 /**
200 * The last selected position we used when notifying
201 */
202 int mOldSelectedPosition = INVALID_POSITION;
203
204 /**
205 * The id of the last selected position we used when notifying
206 */
207 long mOldSelectedRowId = INVALID_ROW_ID;
208
209 /**
210 * Indicates what focusable state is requested when calling setFocusable().
211 * In addition to this, this view has other criteria for actually
212 * determining the focusable state (such as whether its empty or the text
213 * filter is shown).
214 *
215 * @see #setFocusable(boolean)
216 * @see #checkFocus()
217 */
218 private boolean mDesiredFocusableState;
219 private boolean mDesiredFocusableInTouchModeState;
220
Alan Viveretteec8e7202014-10-06 15:33:24 -0700221 /** Lazily-constructed runnable for dispatching selection events. */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 private SelectionNotifier mSelectionNotifier;
Alan Viveretteec8e7202014-10-06 15:33:24 -0700223
224 /** Selection notifier that's waiting for the next layout pass. */
225 private SelectionNotifier mPendingSelectionNotifier;
226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 /**
228 * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
229 * This is used to layout the children during a layout pass.
230 */
231 boolean mBlockLayoutRequests = false;
232
233 public AdapterView(Context context) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700234 this(context, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 }
236
237 public AdapterView(Context context, AttributeSet attrs) {
Alan Viveretted6479ec2013-09-10 17:03:02 -0700238 this(context, attrs, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 }
240
Alan Viverette617feb92013-09-09 18:09:13 -0700241 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
242 this(context, attrs, defStyleAttr, 0);
243 }
244
245 public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
246 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700247
248 // If not explicitly specified this view is important for accessibility.
249 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
250 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
251 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 }
253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 /**
255 * Interface definition for a callback to be invoked when an item in this
256 * AdapterView has been clicked.
257 */
258 public interface OnItemClickListener {
259
260 /**
261 * Callback method to be invoked when an item in this AdapterView has
262 * been clicked.
263 * <p>
264 * Implementers can call getItemAtPosition(position) if they need
265 * to access the data associated with the selected item.
266 *
267 * @param parent The AdapterView where the click happened.
268 * @param view The view within the AdapterView that was clicked (this
269 * will be a view provided by the adapter)
270 * @param position The position of the view in the adapter.
271 * @param id The row id of the item that was clicked.
272 */
273 void onItemClick(AdapterView<?> parent, View view, int position, long id);
274 }
275
276 /**
277 * Register a callback to be invoked when an item in this AdapterView has
278 * been clicked.
279 *
280 * @param listener The callback that will be invoked.
281 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800282 public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 mOnItemClickListener = listener;
284 }
285
286 /**
287 * @return The callback to be invoked with an item in this AdapterView has
288 * been clicked, or null id no callback has been set.
289 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800290 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 public final OnItemClickListener getOnItemClickListener() {
292 return mOnItemClickListener;
293 }
294
295 /**
Alan Viverettefed60952013-09-17 13:00:49 -0700296 * Call the OnItemClickListener, if it is defined. Performs all normal
297 * actions associated with clicking: reporting accessibility event, playing
298 * a sound, etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 *
300 * @param view The view within the AdapterView that was clicked.
301 * @param position The position of the view in the adapter.
302 * @param id The row id of the item that was clicked.
303 * @return True if there was an assigned OnItemClickListener that was
304 * called, false otherwise is returned.
305 */
306 public boolean performItemClick(View view, int position, long id) {
Alan Viverette376c32f2015-06-01 16:41:42 -0700307 final boolean result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 if (mOnItemClickListener != null) {
309 playSoundEffect(SoundEffectConstants.CLICK);
Alan Viverette996a6382014-09-02 16:35:45 -0700310 mOnItemClickListener.onItemClick(this, view, position, id);
Alan Viverette376c32f2015-06-01 16:41:42 -0700311 result = true;
312 } else {
313 result = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 }
315
Alan Viverette376c32f2015-06-01 16:41:42 -0700316 if (view != null) {
317 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
318 }
319 return result;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 }
321
322 /**
323 * Interface definition for a callback to be invoked when an item in this
324 * view has been clicked and held.
325 */
326 public interface OnItemLongClickListener {
327 /**
328 * Callback method to be invoked when an item in this view has been
329 * clicked and held.
330 *
331 * Implementers can call getItemAtPosition(position) if they need to access
332 * the data associated with the selected item.
333 *
334 * @param parent The AbsListView where the click happened
335 * @param view The view within the AbsListView that was clicked
336 * @param position The position of the view in the list
337 * @param id The row id of the item that was clicked
338 *
339 * @return true if the callback consumed the long click, false otherwise
340 */
341 boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id);
342 }
343
344
345 /**
346 * Register a callback to be invoked when an item in this AdapterView has
347 * been clicked and held
348 *
349 * @param listener The callback that will run
350 */
351 public void setOnItemLongClickListener(OnItemLongClickListener listener) {
352 if (!isLongClickable()) {
353 setLongClickable(true);
354 }
355 mOnItemLongClickListener = listener;
356 }
357
358 /**
359 * @return The callback to be invoked with an item in this AdapterView has
360 * been clicked and held, or null id no callback as been set.
361 */
362 public final OnItemLongClickListener getOnItemLongClickListener() {
363 return mOnItemLongClickListener;
364 }
365
366 /**
367 * Interface definition for a callback to be invoked when
368 * an item in this view has been selected.
369 */
370 public interface OnItemSelectedListener {
371 /**
Romain Guyf18c8dd2011-09-16 16:16:36 -0700372 * <p>Callback method to be invoked when an item in this view has been
373 * selected. This callback is invoked only when the newly selected
374 * position is different from the previously selected position or if
375 * there was no selected item.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 *
377 * Impelmenters can call getItemAtPosition(position) if they need to access the
378 * data associated with the selected item.
379 *
380 * @param parent The AdapterView where the selection happened
381 * @param view The view within the AdapterView that was clicked
382 * @param position The position of the view in the adapter
383 * @param id The row id of the item that is selected
384 */
385 void onItemSelected(AdapterView<?> parent, View view, int position, long id);
386
387 /**
388 * Callback method to be invoked when the selection disappears from this
389 * view. The selection can disappear for instance when touch is activated
390 * or when the adapter becomes empty.
391 *
392 * @param parent The AdapterView that now contains no selected item.
393 */
394 void onNothingSelected(AdapterView<?> parent);
395 }
396
397
398 /**
399 * Register a callback to be invoked when an item in this AdapterView has
400 * been selected.
401 *
402 * @param listener The callback that will run
403 */
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800404 public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 mOnItemSelectedListener = listener;
406 }
407
Scott Kennedyed2b5f82015-03-06 17:24:58 -0800408 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 public final OnItemSelectedListener getOnItemSelectedListener() {
410 return mOnItemSelectedListener;
411 }
412
413 /**
414 * Extra menu information provided to the
415 * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
416 * callback when a context menu is brought up for this AdapterView.
417 *
418 */
419 public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
420
421 public AdapterContextMenuInfo(View targetView, int position, long id) {
422 this.targetView = targetView;
423 this.position = position;
424 this.id = id;
425 }
426
427 /**
428 * The child view for which the context menu is being displayed. This
429 * will be one of the children of this AdapterView.
430 */
431 public View targetView;
432
433 /**
434 * The position in the adapter for which the context menu is being
435 * displayed.
436 */
437 public int position;
438
439 /**
440 * The row id of the item for which the context menu is being displayed.
441 */
442 public long id;
443 }
444
445 /**
446 * Returns the adapter currently associated with this widget.
447 *
448 * @return The adapter used to provide this view's content.
449 */
450 public abstract T getAdapter();
451
452 /**
453 * Sets the adapter that provides the data and the views to represent the data
454 * in this widget.
455 *
456 * @param adapter The adapter to use to create this view's content.
457 */
458 public abstract void setAdapter(T adapter);
459
460 /**
461 * This method is not supported and throws an UnsupportedOperationException when called.
462 *
463 * @param child Ignored.
464 *
465 * @throws UnsupportedOperationException Every time this method is invoked.
466 */
467 @Override
468 public void addView(View child) {
469 throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
470 }
471
472 /**
473 * This method is not supported and throws an UnsupportedOperationException when called.
474 *
475 * @param child Ignored.
476 * @param index Ignored.
477 *
478 * @throws UnsupportedOperationException Every time this method is invoked.
479 */
480 @Override
481 public void addView(View child, int index) {
482 throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
483 }
484
485 /**
486 * This method is not supported and throws an UnsupportedOperationException when called.
487 *
488 * @param child Ignored.
489 * @param params Ignored.
490 *
491 * @throws UnsupportedOperationException Every time this method is invoked.
492 */
493 @Override
494 public void addView(View child, LayoutParams params) {
495 throw new UnsupportedOperationException("addView(View, LayoutParams) "
496 + "is not supported in AdapterView");
497 }
498
499 /**
500 * This method is not supported and throws an UnsupportedOperationException when called.
501 *
502 * @param child Ignored.
503 * @param index Ignored.
504 * @param params Ignored.
505 *
506 * @throws UnsupportedOperationException Every time this method is invoked.
507 */
508 @Override
509 public void addView(View child, int index, LayoutParams params) {
510 throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
511 + "is not supported in AdapterView");
512 }
513
514 /**
515 * This method is not supported and throws an UnsupportedOperationException when called.
516 *
517 * @param child Ignored.
518 *
519 * @throws UnsupportedOperationException Every time this method is invoked.
520 */
521 @Override
522 public void removeView(View child) {
523 throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
524 }
525
526 /**
527 * This method is not supported and throws an UnsupportedOperationException when called.
528 *
529 * @param index Ignored.
530 *
531 * @throws UnsupportedOperationException Every time this method is invoked.
532 */
533 @Override
534 public void removeViewAt(int index) {
535 throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
536 }
537
538 /**
539 * This method is not supported and throws an UnsupportedOperationException when called.
540 *
541 * @throws UnsupportedOperationException Every time this method is invoked.
542 */
543 @Override
544 public void removeAllViews() {
545 throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
546 }
547
548 @Override
549 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
550 mLayoutHeight = getHeight();
551 }
552
553 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000554 * Return the position of the currently selected item within the adapter's data set
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 *
Alan Viverette31ff78b12015-06-04 17:18:34 +0000556 * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 */
558 @ViewDebug.CapturedViewProperty
559 public int getSelectedItemPosition() {
560 return mNextSelectedPosition;
561 }
562
563 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000564 * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
565 * if nothing is selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 */
567 @ViewDebug.CapturedViewProperty
568 public long getSelectedItemId() {
569 return mNextSelectedRowId;
570 }
571
572 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000573 * @return The view corresponding to the currently selected item, or null
574 * if nothing is selected
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 */
576 public abstract View getSelectedView();
577
578 /**
Alan Viverette31ff78b12015-06-04 17:18:34 +0000579 * @return The data corresponding to the currently selected item, or
580 * null if there is nothing selected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581 */
582 public Object getSelectedItem() {
Alan Viverette31ff78b12015-06-04 17:18:34 +0000583 T adapter = getAdapter();
584 int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
586 return adapter.getItem(selection);
587 } else {
588 return null;
589 }
590 }
591
592 /**
593 * @return The number of items owned by the Adapter associated with this
594 * AdapterView. (This is the number of data items, which may be
Chet Haase5e25c2c2010-09-16 11:15:56 -0700595 * larger than the number of visible views.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800596 */
597 @ViewDebug.CapturedViewProperty
598 public int getCount() {
599 return mItemCount;
600 }
601
602 /**
Alan Viverette92539d52015-09-14 10:49:25 -0400603 * Returns the position within the adapter's data set for the view, where
604 * view is a an adapter item or a descendant of an adapter item.
605 * <p>
606 * <strong>Note:</strong> The result of this method only reflects the
607 * position of the data bound to <var>view</var> during the most recent
608 * layout pass. If the adapter's data set has changed without a subsequent
609 * layout pass, the position returned by this method may not match the
610 * current position of the data within the adapter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 *
Alan Viverette92539d52015-09-14 10:49:25 -0400612 * @param view an adapter item, or a descendant of an adapter item. This
613 * must be visible in this AdapterView at the time of the call.
614 * @return the position within the adapter's data set of the view, or
615 * {@link #INVALID_POSITION} if the view does not correspond to a
616 * list item (or it is not currently visible)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 */
618 public int getPositionForView(View view) {
619 View listItem = view;
620 try {
621 View v;
Alan Viverette898c7042015-08-26 15:21:39 -0400622 while ((v = (View) listItem.getParent()) != null && !v.equals(this)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 listItem = v;
624 }
625 } catch (ClassCastException e) {
626 // We made it up to the window without find this list view
627 return INVALID_POSITION;
628 }
629
Alan Viverette898c7042015-08-26 15:21:39 -0400630 if (listItem != null) {
631 // Search the children for the list item
632 final int childCount = getChildCount();
633 for (int i = 0; i < childCount; i++) {
634 if (getChildAt(i).equals(listItem)) {
635 return mFirstPosition + i;
636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 }
638 }
639
640 // Child not found!
641 return INVALID_POSITION;
642 }
643
644 /**
645 * Returns the position within the adapter's data set for the first item
646 * displayed on screen.
647 *
648 * @return The position within the adapter's data set
649 */
650 public int getFirstVisiblePosition() {
651 return mFirstPosition;
652 }
653
654 /**
655 * Returns the position within the adapter's data set for the last item
656 * displayed on screen.
657 *
658 * @return The position within the adapter's data set
659 */
660 public int getLastVisiblePosition() {
661 return mFirstPosition + getChildCount() - 1;
662 }
663
664 /**
svetoslavganov75986cf2009-05-14 22:28:01 -0700665 * Sets the currently selected item. To support accessibility subclasses that
666 * override this method must invoke the overriden super method first.
667 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 * @param position Index (starting at 0) of the data item to be selected.
669 */
670 public abstract void setSelection(int position);
671
672 /**
673 * Sets the view to show if the adapter is empty
674 */
Adam Cohen1480fdd2010-08-25 17:24:53 -0700675 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 public void setEmptyView(View emptyView) {
677 mEmptyView = emptyView;
678
Svetoslav Ganov42138042012-03-20 11:51:39 -0700679 // If not explicitly specified this view is important for accessibility.
Mindy Pereiraf9373132012-04-16 08:58:53 -0700680 if (emptyView != null
681 && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700682 emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
683 }
684
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 final T adapter = getAdapter();
686 final boolean empty = ((adapter == null) || adapter.isEmpty());
687 updateEmptyStatus(empty);
688 }
689
690 /**
691 * When the current adapter is empty, the AdapterView can display a special view
Mark Doliner9525f2a2014-01-02 11:17:47 -0800692 * called the empty view. The empty view is used to provide feedback to the user
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 * that no data is available in this AdapterView.
694 *
695 * @return The view to show if the adapter is empty.
696 */
697 public View getEmptyView() {
698 return mEmptyView;
699 }
700
701 /**
702 * Indicates whether this view is in filter mode. Filter mode can for instance
703 * be enabled by a user when typing on the keyboard.
704 *
705 * @return True if the view is in filter mode, false otherwise.
706 */
707 boolean isInFilterMode() {
708 return false;
709 }
710
711 @Override
712 public void setFocusable(boolean focusable) {
713 final T adapter = getAdapter();
714 final boolean empty = adapter == null || adapter.getCount() == 0;
715
716 mDesiredFocusableState = focusable;
717 if (!focusable) {
718 mDesiredFocusableInTouchModeState = false;
719 }
720
721 super.setFocusable(focusable && (!empty || isInFilterMode()));
722 }
723
724 @Override
725 public void setFocusableInTouchMode(boolean focusable) {
726 final T adapter = getAdapter();
727 final boolean empty = adapter == null || adapter.getCount() == 0;
728
729 mDesiredFocusableInTouchModeState = focusable;
730 if (focusable) {
731 mDesiredFocusableState = true;
732 }
733
734 super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
735 }
736
737 void checkFocus() {
738 final T adapter = getAdapter();
739 final boolean empty = adapter == null || adapter.getCount() == 0;
740 final boolean focusable = !empty || isInFilterMode();
741 // The order in which we set focusable in touch mode/focusable may matter
742 // for the client, see View.setFocusableInTouchMode() comments for more
743 // details
744 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
745 super.setFocusable(focusable && mDesiredFocusableState);
746 if (mEmptyView != null) {
747 updateEmptyStatus((adapter == null) || adapter.isEmpty());
748 }
749 }
750
751 /**
752 * Update the status of the list based on the empty parameter. If empty is true and
753 * we have an empty view, display it. In all the other cases, make sure that the listview
754 * is VISIBLE and that the empty view is GONE (if it's not null).
755 */
756 private void updateEmptyStatus(boolean empty) {
757 if (isInFilterMode()) {
758 empty = false;
759 }
760
761 if (empty) {
762 if (mEmptyView != null) {
763 mEmptyView.setVisibility(View.VISIBLE);
764 setVisibility(View.GONE);
765 } else {
766 // If the caller just removed our empty view, make sure the list view is visible
767 setVisibility(View.VISIBLE);
768 }
769
770 // We are now GONE, so pending layouts will not be dispatched.
771 // Force one here to make sure that the state of the list matches
772 // the state of the adapter.
773 if (mDataChanged) {
774 this.onLayout(false, mLeft, mTop, mRight, mBottom);
775 }
776 } else {
777 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
778 setVisibility(View.VISIBLE);
779 }
780 }
781
782 /**
783 * Gets the data associated with the specified position in the list.
784 *
785 * @param position Which data to get
786 * @return The data associated with the specified position in the list
787 */
788 public Object getItemAtPosition(int position) {
789 T adapter = getAdapter();
790 return (adapter == null || position < 0) ? null : adapter.getItem(position);
791 }
792
793 public long getItemIdAtPosition(int position) {
794 T adapter = getAdapter();
795 return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
796 }
797
798 @Override
799 public void setOnClickListener(OnClickListener l) {
800 throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
801 + "You probably want setOnItemClickListener instead");
802 }
803
804 /**
805 * Override to prevent freezing of any views created by the adapter.
806 */
807 @Override
808 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
809 dispatchFreezeSelfOnly(container);
810 }
811
812 /**
813 * Override to prevent thawing of any views created by the adapter.
814 */
815 @Override
816 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
817 dispatchThawSelfOnly(container);
Doris Liub7714ae2015-09-09 13:59:32 -0700818 handleDataChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800819 }
820
821 class AdapterDataSetObserver extends DataSetObserver {
822
823 private Parcelable mInstanceState = null;
824
825 @Override
826 public void onChanged() {
827 mDataChanged = true;
828 mOldItemCount = mItemCount;
829 mItemCount = getAdapter().getCount();
830
831 // Detect the case where a cursor that was previously invalidated has
832 // been repopulated with new data.
833 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
834 && mOldItemCount == 0 && mItemCount > 0) {
835 AdapterView.this.onRestoreInstanceState(mInstanceState);
836 mInstanceState = null;
837 } else {
838 rememberSyncState();
839 }
840 checkFocus();
841 requestLayout();
842 }
843
844 @Override
845 public void onInvalidated() {
846 mDataChanged = true;
847
848 if (AdapterView.this.getAdapter().hasStableIds()) {
849 // Remember the current state for the case where our hosting activity is being
850 // stopped and later restarted
851 mInstanceState = AdapterView.this.onSaveInstanceState();
852 }
853
854 // Data is invalid so we should reset our state
855 mOldItemCount = mItemCount;
856 mItemCount = 0;
857 mSelectedPosition = INVALID_POSITION;
858 mSelectedRowId = INVALID_ROW_ID;
859 mNextSelectedPosition = INVALID_POSITION;
860 mNextSelectedRowId = INVALID_ROW_ID;
861 mNeedSync = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862
863 checkFocus();
864 requestLayout();
865 }
866
867 public void clearSavedState() {
868 mInstanceState = null;
869 }
870 }
871
Adam Powell9c17a4c12010-08-30 18:04:15 -0700872 @Override
873 protected void onDetachedFromWindow() {
874 super.onDetachedFromWindow();
875 removeCallbacks(mSelectionNotifier);
876 }
877
878 private class SelectionNotifier implements Runnable {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 public void run() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700880 mPendingSelectionNotifier = null;
881
Svet Ganov99a82432014-10-24 16:27:38 -0700882 if (mDataChanged && getViewRootImpl() != null
883 && getViewRootImpl().isLayoutRequested()) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700884 // Data has changed between when this SelectionNotifier was
885 // posted and now. Postpone the notification until the next
886 // layout is complete and we run checkSelectionChanged().
Adam Powell9c17a4c12010-08-30 18:04:15 -0700887 if (getAdapter() != null) {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700888 mPendingSelectionNotifier = this;
Adam Powell9c17a4c12010-08-30 18:04:15 -0700889 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700891 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892 }
893 }
894 }
895
896 void selectionChanged() {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700897 // We're about to post or run the selection notifier, so we don't need
898 // a pending notifier.
899 mPendingSelectionNotifier = null;
900
Svetoslav Ganov42138042012-03-20 11:51:39 -0700901 if (mOnItemSelectedListener != null
902 || AccessibilityManager.getInstance(mContext).isEnabled()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 if (mInLayout || mBlockLayoutRequests) {
904 // If we are in a layout traversal, defer notification
905 // by posting. This ensures that the view tree is
Alan Viveretteec8e7202014-10-06 15:33:24 -0700906 // in a consistent state and is able to accommodate
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 // new layout or invalidate requests.
908 if (mSelectionNotifier == null) {
909 mSelectionNotifier = new SelectionNotifier();
Alan Viveretteec8e7202014-10-06 15:33:24 -0700910 } else {
911 removeCallbacks(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 }
Adam Powell9c17a4c12010-08-30 18:04:15 -0700913 post(mSelectionNotifier);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800914 } else {
Alan Viveretteec8e7202014-10-06 15:33:24 -0700915 dispatchOnItemSelected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
917 }
918 }
919
Alan Viveretteec8e7202014-10-06 15:33:24 -0700920 private void dispatchOnItemSelected() {
921 fireOnSelected();
922 performAccessibilityActionsOnSelected();
923 }
924
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800925 private void fireOnSelected() {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700926 if (mOnItemSelectedListener == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700928 }
929 final int selection = getSelectedItemPosition();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 if (selection >= 0) {
931 View v = getSelectedView();
932 mOnItemSelectedListener.onItemSelected(this, v, selection,
933 getAdapter().getItemId(selection));
934 } else {
935 mOnItemSelectedListener.onNothingSelected(this);
936 }
937 }
938
Svetoslav Ganov42138042012-03-20 11:51:39 -0700939 private void performAccessibilityActionsOnSelected() {
940 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
941 return;
942 }
943 final int position = getSelectedItemPosition();
944 if (position >= 0) {
945 // we fire selection events here not in View
946 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
947 }
948 }
949
Alan Viverettea54956a2015-01-07 16:05:02 -0800950 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800951 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800952 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700953 View selectedView = getSelectedView();
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700954 if (selectedView != null && selectedView.getVisibility() == VISIBLE
955 && selectedView.dispatchPopulateAccessibilityEvent(event)) {
956 return true;
Svetoslav Ganov0b0a41d2011-09-07 18:06:03 -0700957 }
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700958 return false;
959 }
960
Alan Viverettea54956a2015-01-07 16:05:02 -0800961 /** @hide */
Svetoslav Ganovbad9d972011-05-27 16:37:55 -0700962 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800963 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
964 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Svetoslav Ganov6179ea32011-06-28 01:12:41 -0700965 // Add a record for ourselves as well.
966 AccessibilityEvent record = AccessibilityEvent.obtain();
967 onInitializeAccessibilityEvent(record);
968 // Populate with the text of the requesting child.
969 child.dispatchPopulateAccessibilityEvent(record);
970 event.appendRecord(record);
971 return true;
972 }
973 return false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700974 }
Adam Powell3fb3d7c2011-04-22 17:08:55 -0700975
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800976 @Override
977 public CharSequence getAccessibilityClassName() {
978 return AdapterView.class.getName();
979 }
980
Alan Viverettea54956a2015-01-07 16:05:02 -0800981 /** @hide */
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700982 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800983 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
984 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700985 info.setScrollable(isScrollableForAccessibility());
986 View selectedView = getSelectedView();
987 if (selectedView != null) {
988 info.setEnabled(selectedView.isEnabled());
989 }
990 }
991
Alan Viverettea54956a2015-01-07 16:05:02 -0800992 /** @hide */
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700993 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800994 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
995 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700996 event.setScrollable(isScrollableForAccessibility());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700997 View selectedView = getSelectedView();
998 if (selectedView != null) {
999 event.setEnabled(selectedView.isEnabled());
1000 }
Svetoslav Ganoveb0c52e2011-10-10 18:15:41 -07001001 event.setCurrentItemIndex(getSelectedItemPosition());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001002 event.setFromIndex(getFirstVisiblePosition());
1003 event.setToIndex(getLastVisiblePosition());
Svetoslav Ganov0e6fa8b2011-10-19 12:38:04 -07001004 event.setItemCount(getCount());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001005 }
1006
1007 private boolean isScrollableForAccessibility() {
Svetoslav Ganov98348512011-10-10 17:18:19 -07001008 T adapter = getAdapter();
1009 if (adapter != null) {
1010 final int itemCount = adapter.getCount();
1011 return itemCount > 0
1012 && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1);
1013 }
1014 return false;
svetoslavganov75986cf2009-05-14 22:28:01 -07001015 }
1016
1017 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 protected boolean canAnimate() {
1019 return super.canAnimate() && mItemCount > 0;
1020 }
1021
1022 void handleDataChanged() {
1023 final int count = mItemCount;
1024 boolean found = false;
1025
1026 if (count > 0) {
1027
1028 int newPos;
1029
1030 // Find the row we are supposed to sync to
1031 if (mNeedSync) {
1032 // Update this first, since setNextSelectedPositionInt inspects
1033 // it
1034 mNeedSync = false;
1035
1036 // See if we can find a position in the new data with the same
1037 // id as the old selection
1038 newPos = findSyncPosition();
1039 if (newPos >= 0) {
1040 // Verify that new selection is selectable
1041 int selectablePos = lookForSelectablePosition(newPos, true);
1042 if (selectablePos == newPos) {
1043 // Same row id is selected
1044 setNextSelectedPositionInt(newPos);
1045 found = true;
1046 }
1047 }
1048 }
1049 if (!found) {
1050 // Try to use the same position if we can't find matching data
1051 newPos = getSelectedItemPosition();
1052
1053 // Pin position to the available range
1054 if (newPos >= count) {
1055 newPos = count - 1;
1056 }
1057 if (newPos < 0) {
1058 newPos = 0;
1059 }
1060
1061 // Make sure we select something selectable -- first look down
1062 int selectablePos = lookForSelectablePosition(newPos, true);
1063 if (selectablePos < 0) {
1064 // Looking down didn't work -- try looking up
1065 selectablePos = lookForSelectablePosition(newPos, false);
1066 }
1067 if (selectablePos >= 0) {
1068 setNextSelectedPositionInt(selectablePos);
1069 checkSelectionChanged();
1070 found = true;
1071 }
1072 }
1073 }
1074 if (!found) {
1075 // Nothing is selected
1076 mSelectedPosition = INVALID_POSITION;
1077 mSelectedRowId = INVALID_ROW_ID;
1078 mNextSelectedPosition = INVALID_POSITION;
1079 mNextSelectedRowId = INVALID_ROW_ID;
1080 mNeedSync = false;
1081 checkSelectionChanged();
1082 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001083
Svetoslav00dbe812013-06-10 12:51:09 -07001084 notifySubtreeAccessibilityStateChangedIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 }
1086
Alan Viveretteec8e7202014-10-06 15:33:24 -07001087 /**
1088 * Called after layout to determine whether the selection position needs to
1089 * be updated. Also used to fire any pending selection events.
1090 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 void checkSelectionChanged() {
1092 if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
1093 selectionChanged();
1094 mOldSelectedPosition = mSelectedPosition;
1095 mOldSelectedRowId = mSelectedRowId;
1096 }
Alan Viveretteec8e7202014-10-06 15:33:24 -07001097
1098 // If we have a pending selection notification -- and we won't if we
1099 // just fired one in selectionChanged() -- run it now.
1100 if (mPendingSelectionNotifier != null) {
1101 mPendingSelectionNotifier.run();
1102 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 }
1104
1105 /**
1106 * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
1107 * and then alternates between moving up and moving down until 1) we find the right position, or
1108 * 2) we run out of time, or 3) we have looked at every position
1109 *
1110 * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
1111 * be found
1112 */
1113 int findSyncPosition() {
1114 int count = mItemCount;
1115
1116 if (count == 0) {
1117 return INVALID_POSITION;
1118 }
1119
1120 long idToMatch = mSyncRowId;
1121 int seed = mSyncPosition;
1122
1123 // If there isn't a selection don't hunt for it
1124 if (idToMatch == INVALID_ROW_ID) {
1125 return INVALID_POSITION;
1126 }
1127
1128 // Pin seed to reasonable values
1129 seed = Math.max(0, seed);
1130 seed = Math.min(count - 1, seed);
1131
1132 long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
1133
1134 long rowId;
1135
1136 // first position scanned so far
1137 int first = seed;
1138
1139 // last position scanned so far
1140 int last = seed;
1141
1142 // True if we should move down on the next iteration
1143 boolean next = false;
1144
1145 // True when we have looked at the first item in the data
1146 boolean hitFirst;
1147
1148 // True when we have looked at the last item in the data
1149 boolean hitLast;
1150
1151 // Get the item ID locally (instead of getItemIdAtPosition), so
1152 // we need the adapter
1153 T adapter = getAdapter();
1154 if (adapter == null) {
1155 return INVALID_POSITION;
1156 }
1157
1158 while (SystemClock.uptimeMillis() <= endTime) {
1159 rowId = adapter.getItemId(seed);
1160 if (rowId == idToMatch) {
1161 // Found it!
1162 return seed;
1163 }
1164
1165 hitLast = last == count - 1;
1166 hitFirst = first == 0;
1167
1168 if (hitLast && hitFirst) {
1169 // Looked at everything
1170 break;
1171 }
1172
1173 if (hitFirst || (next && !hitLast)) {
1174 // Either we hit the top, or we are trying to move down
1175 last++;
1176 seed = last;
1177 // Try going up next time
1178 next = false;
1179 } else if (hitLast || (!next && !hitFirst)) {
1180 // Either we hit the bottom, or we are trying to move up
1181 first--;
1182 seed = first;
1183 // Try going down next time
1184 next = true;
1185 }
1186
1187 }
1188
1189 return INVALID_POSITION;
1190 }
1191
1192 /**
1193 * Find a position that can be selected (i.e., is not a separator).
1194 *
1195 * @param position The starting position to look at.
1196 * @param lookDown Whether to look down for other positions.
1197 * @return The next selectable position starting at position and then searching either up or
1198 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1199 */
1200 int lookForSelectablePosition(int position, boolean lookDown) {
1201 return position;
1202 }
1203
1204 /**
1205 * Utility to keep mSelectedPosition and mSelectedRowId in sync
1206 * @param position Our current position
1207 */
1208 void setSelectedPositionInt(int position) {
1209 mSelectedPosition = position;
1210 mSelectedRowId = getItemIdAtPosition(position);
1211 }
1212
1213 /**
1214 * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
1215 * @param position Intended value for mSelectedPosition the next time we go
1216 * through layout
1217 */
1218 void setNextSelectedPositionInt(int position) {
1219 mNextSelectedPosition = position;
1220 mNextSelectedRowId = getItemIdAtPosition(position);
1221 // If we are trying to sync to the selection, update that too
1222 if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
1223 mSyncPosition = position;
1224 mSyncRowId = mNextSelectedRowId;
1225 }
1226 }
1227
1228 /**
1229 * Remember enough information to restore the screen state when the data has
1230 * changed.
1231 *
1232 */
1233 void rememberSyncState() {
1234 if (getChildCount() > 0) {
1235 mNeedSync = true;
1236 mSyncHeight = mLayoutHeight;
1237 if (mSelectedPosition >= 0) {
1238 // Sync the selection state
1239 View v = getChildAt(mSelectedPosition - mFirstPosition);
1240 mSyncRowId = mNextSelectedRowId;
1241 mSyncPosition = mNextSelectedPosition;
1242 if (v != null) {
1243 mSpecificTop = v.getTop();
1244 }
1245 mSyncMode = SYNC_SELECTED_POSITION;
1246 } else {
1247 // Sync the based on the offset of the first view
1248 View v = getChildAt(0);
1249 T adapter = getAdapter();
1250 if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
1251 mSyncRowId = adapter.getItemId(mFirstPosition);
1252 } else {
1253 mSyncRowId = NO_ID;
1254 }
1255 mSyncPosition = mFirstPosition;
1256 if (v != null) {
1257 mSpecificTop = v.getTop();
1258 }
1259 mSyncMode = SYNC_FIRST_POSITION;
1260 }
1261 }
1262 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07001263
1264 /** @hide */
1265 @Override
1266 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1267 super.encodeProperties(encoder);
1268
1269 encoder.addProperty("scrolling:firstPosition", mFirstPosition);
1270 encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition);
1271 encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId);
1272 encoder.addProperty("list:selectedPosition", mSelectedPosition);
1273 encoder.addProperty("list:itemCount", mItemCount);
1274 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275}