blob: f42999d283cbade93f8f672aa7bfb33f314187bb [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
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080019import com.android.internal.R;
Jeff Brown4e6319b2010-12-13 10:36:51 -080020import com.android.internal.util.Predicate;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080021import com.google.android.collect.Lists;
22
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070024import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.TypedArray;
26import android.graphics.Canvas;
Romain Guy8f1344f52009-05-15 16:03:59 -070027import android.graphics.Paint;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080028import android.graphics.PixelFormat;
29import android.graphics.Rect;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080030import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.util.AttributeSet;
alanv30ee76c2012-09-07 10:31:16 -070032import android.util.MathUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.util.SparseBooleanArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.view.FocusFinder;
35import android.view.KeyEvent;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080036import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewGroup;
40import android.view.ViewParent;
alanv27093de2012-09-10 11:10:25 -070041import android.view.ViewRootImpl;
svetoslavganov75986cf2009-05-14 22:28:01 -070042import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080043import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette11f796e2013-04-16 14:32:17 -070044import android.view.accessibility.AccessibilityNodeProvider;
Winson Chung499cb9f2010-07-16 11:18:17 -070045import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.util.ArrayList;
48
49/*
50 * Implementation Notes:
51 *
52 * Some terminology:
53 *
54 * index - index of the items that are currently visible
55 * position - index of the items in the cursor
56 */
57
58
59/**
60 * A view that shows items in a vertically scrolling list. The items
61 * come from the {@link ListAdapter} associated with this view.
62 *
Scott Main4c359b72012-07-24 15:51:27 -070063 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
64 * guide.</p>
Scott Main41ec6532010-08-19 16:57:07 -070065 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 * @attr ref android.R.styleable#ListView_entries
67 * @attr ref android.R.styleable#ListView_divider
68 * @attr ref android.R.styleable#ListView_dividerHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 * @attr ref android.R.styleable#ListView_headerDividersEnabled
70 * @attr ref android.R.styleable#ListView_footerDividersEnabled
71 */
Winson Chung499cb9f2010-07-16 11:18:17 -070072@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073public class ListView extends AbsListView {
74 /**
75 * Used to indicate a no preference for a position type.
76 */
77 static final int NO_POSITION = -1;
78
79 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 * When arrow scrolling, ListView will never scroll more than this factor
81 * times the height of the list.
82 */
83 private static final float MAX_SCROLL_FACTOR = 0.33f;
84
85 /**
86 * When arrow scrolling, need a certain amount of pixels to preview next
87 * items. This is usually the fading edge, but if that is small enough,
88 * we want to make sure we preview at least this many pixels.
89 */
90 private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
91
92 /**
93 * A class that represents a fixed view in a list, for example a header at the top
94 * or a footer at the bottom.
95 */
96 public class FixedViewInfo {
97 /** The view to add to the list */
98 public View view;
99 /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
100 public Object data;
101 /** <code>true</code> if the fixed view should be selectable in the list */
102 public boolean isSelectable;
103 }
104
105 private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
106 private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
107
108 Drawable mDivider;
109 int mDividerHeight;
Adam Cohenfb603862010-12-17 12:03:17 -0800110
Adam Powell637d3372010-08-25 14:37:03 -0700111 Drawable mOverScrollHeader;
112 Drawable mOverScrollFooter;
113
Romain Guy24443ea2009-05-11 11:56:30 -0700114 private boolean mIsCacheColorOpaque;
115 private boolean mDividerIsOpaque;
Romain Guy24443ea2009-05-11 11:56:30 -0700116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 private boolean mHeaderDividersEnabled;
118 private boolean mFooterDividersEnabled;
119
120 private boolean mAreAllItemsSelectable = true;
121
122 private boolean mItemsCanFocus = false;
123
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 // used for temporary calculations.
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700125 private final Rect mTempRect = new Rect();
Romain Guya02903f2009-05-23 13:26:46 -0700126 private Paint mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127
128 // the single allocated result per list view; kinda cheesey but avoids
129 // allocating these thingies too often.
Romain Guy9c3184cc2010-02-25 17:32:54 -0800130 private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131
Adam Powell9bf3c122010-02-26 11:32:07 -0800132 // Keeps focused children visible through resizes
133 private FocusSelector mFocusSelector;
Adam Powell8350f7d2010-07-28 14:27:28 -0700134
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 public ListView(Context context) {
136 this(context, null);
137 }
138
139 public ListView(Context context, AttributeSet attrs) {
140 this(context, attrs, com.android.internal.R.attr.listViewStyle);
141 }
142
143 public ListView(Context context, AttributeSet attrs, int defStyle) {
144 super(context, attrs, defStyle);
145
146 TypedArray a = context.obtainStyledAttributes(attrs,
147 com.android.internal.R.styleable.ListView, defStyle, 0);
148
149 CharSequence[] entries = a.getTextArray(
150 com.android.internal.R.styleable.ListView_entries);
151 if (entries != null) {
152 setAdapter(new ArrayAdapter<CharSequence>(context,
153 com.android.internal.R.layout.simple_list_item_1, entries));
154 }
155
156 final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
157 if (d != null) {
158 // If a divider is specified use its intrinsic height for divider height
159 setDivider(d);
160 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -0800161
Adam Powell637d3372010-08-25 14:37:03 -0700162 final Drawable osHeader = a.getDrawable(
163 com.android.internal.R.styleable.ListView_overScrollHeader);
164 if (osHeader != null) {
165 setOverscrollHeader(osHeader);
166 }
167
168 final Drawable osFooter = a.getDrawable(
169 com.android.internal.R.styleable.ListView_overScrollFooter);
170 if (osFooter != null) {
171 setOverscrollFooter(osFooter);
172 }
173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 // Use the height specified, zero being the default
175 final int dividerHeight = a.getDimensionPixelSize(
176 com.android.internal.R.styleable.ListView_dividerHeight, 0);
177 if (dividerHeight != 0) {
178 setDividerHeight(dividerHeight);
179 }
180
181 mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
182 mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
183
184 a.recycle();
185 }
186
187 /**
188 * @return The maximum amount a list view will scroll in response to
189 * an arrow event.
190 */
191 public int getMaxScrollAmount() {
192 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
193 }
194
195 /**
196 * Make sure views are touching the top or bottom edge, as appropriate for
197 * our gravity
198 */
199 private void adjustViewsUpOrDown() {
200 final int childCount = getChildCount();
201 int delta;
202
203 if (childCount > 0) {
204 View child;
205
206 if (!mStackFromBottom) {
207 // Uh-oh -- we came up short. Slide all views up to make them
208 // align with the top
209 child = getChildAt(0);
210 delta = child.getTop() - mListPadding.top;
211 if (mFirstPosition != 0) {
212 // It's OK to have some space above the first item if it is
213 // part of the vertical spacing
214 delta -= mDividerHeight;
215 }
216 if (delta < 0) {
217 // We only are looking to see if we are too low, not too high
218 delta = 0;
219 }
220 } else {
221 // we are too high, slide all views down to align with bottom
222 child = getChildAt(childCount - 1);
223 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
224
225 if (mFirstPosition + childCount < mItemCount) {
226 // It's OK to have some space below the last item if it is
227 // part of the vertical spacing
228 delta += mDividerHeight;
229 }
230
231 if (delta > 0) {
232 delta = 0;
233 }
234 }
235
236 if (delta != 0) {
237 offsetChildrenTopAndBottom(-delta);
238 }
239 }
240 }
241
242 /**
243 * Add a fixed view to appear at the top of the list. If addHeaderView is
244 * called more than once, the views will appear in the order they were
245 * added. Views added using this call can take focus if they want.
246 * <p>
247 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800248 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 * views.
250 *
251 * @param v The view to add.
252 * @param data Data to associate with this view
253 * @param isSelectable whether the item is selectable
254 */
255 public void addHeaderView(View v, Object data, boolean isSelectable) {
256
Marco Nelissen22c04a32011-04-19 14:07:55 -0700257 if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 throw new IllegalStateException(
259 "Cannot add header view to list -- setAdapter has already been called.");
260 }
261
262 FixedViewInfo info = new FixedViewInfo();
263 info.view = v;
264 info.data = data;
265 info.isSelectable = isSelectable;
266 mHeaderViewInfos.add(info);
Marco Nelissen22c04a32011-04-19 14:07:55 -0700267
268 // in the case of re-adding a header view, or adding one later on,
269 // we need to notify the observer
Adam Powell247a0f02011-09-13 13:11:29 -0700270 if (mAdapter != null && mDataSetObserver != null) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700271 mDataSetObserver.onChanged();
272 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 }
274
275 /**
276 * Add a fixed view to appear at the top of the list. If addHeaderView is
277 * called more than once, the views will appear in the order they were
278 * added. Views added using this call can take focus if they want.
279 * <p>
280 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800281 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 * views.
283 *
284 * @param v The view to add.
285 */
286 public void addHeaderView(View v) {
287 addHeaderView(v, null, true);
288 }
289
290 @Override
291 public int getHeaderViewsCount() {
292 return mHeaderViewInfos.size();
293 }
294
295 /**
296 * Removes a previously-added header view.
297 *
298 * @param v The view to remove
299 * @return true if the view was removed, false if the view was not a header
300 * view
301 */
302 public boolean removeHeaderView(View v) {
303 if (mHeaderViewInfos.size() > 0) {
304 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700305 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700306 if (mDataSetObserver != null) {
307 mDataSetObserver.onChanged();
308 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 result = true;
310 }
311 removeFixedViewInfo(v, mHeaderViewInfos);
312 return result;
313 }
314 return false;
315 }
316
317 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
318 int len = where.size();
319 for (int i = 0; i < len; ++i) {
320 FixedViewInfo info = where.get(i);
321 if (info.view == v) {
322 where.remove(i);
323 break;
324 }
325 }
326 }
327
328 /**
329 * Add a fixed view to appear at the bottom of the list. If addFooterView is
330 * called more than once, the views will appear in the order they were
331 * added. Views added using this call can take focus if they want.
332 * <p>
333 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800334 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 * views.
336 *
337 * @param v The view to add.
338 * @param data Data to associate with this view
339 * @param isSelectable true if the footer view can be selected
340 */
341 public void addFooterView(View v, Object data, boolean isSelectable) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700342
Marco Nelissen63f96892011-04-20 13:42:25 -0700343 // NOTE: do not enforce the adapter being null here, since unlike in
344 // addHeaderView, it was never enforced here, and so existing apps are
345 // relying on being able to add a footer and then calling setAdapter to
346 // force creation of the HeaderViewListAdapter wrapper
Marco Nelissen22c04a32011-04-19 14:07:55 -0700347
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 FixedViewInfo info = new FixedViewInfo();
349 info.view = v;
350 info.data = data;
351 info.isSelectable = isSelectable;
352 mFooterViewInfos.add(info);
353
354 // in the case of re-adding a footer view, or adding one later on,
355 // we need to notify the observer
Adam Powell247a0f02011-09-13 13:11:29 -0700356 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 mDataSetObserver.onChanged();
358 }
359 }
360
361 /**
362 * Add a fixed view to appear at the bottom of the list. If addFooterView is called more
363 * than once, the views will appear in the order they were added. Views added using
364 * this call can take focus if they want.
365 * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800366 * cursor with one that will also account for header and footer views.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 *
368 *
369 * @param v The view to add.
370 */
371 public void addFooterView(View v) {
372 addFooterView(v, null, true);
373 }
374
375 @Override
376 public int getFooterViewsCount() {
377 return mFooterViewInfos.size();
378 }
379
380 /**
381 * Removes a previously-added footer view.
382 *
383 * @param v The view to remove
384 * @return
385 * true if the view was removed, false if the view was not a footer view
386 */
387 public boolean removeFooterView(View v) {
388 if (mFooterViewInfos.size() > 0) {
389 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700390 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700391 if (mDataSetObserver != null) {
392 mDataSetObserver.onChanged();
393 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 result = true;
395 }
396 removeFixedViewInfo(v, mFooterViewInfos);
397 return result;
398 }
399 return false;
400 }
401
402 /**
403 * Returns the adapter currently in use in this ListView. The returned adapter
404 * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
405 * might be a {@link WrapperListAdapter}.
406 *
407 * @return The adapter currently used to display data in this ListView.
408 *
409 * @see #setAdapter(ListAdapter)
410 */
411 @Override
412 public ListAdapter getAdapter() {
413 return mAdapter;
414 }
415
416 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700417 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
418 * through the specified intent.
419 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
420 */
421 @android.view.RemotableViewMethod
422 public void setRemoteViewsAdapter(Intent intent) {
423 super.setRemoteViewsAdapter(intent);
424 }
425
426 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 * Sets the data behind this ListView.
428 *
429 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
430 * depending on the ListView features currently in use. For instance, adding
431 * headers and/or footers will cause the adapter to be wrapped.
432 *
433 * @param adapter The ListAdapter which is responsible for maintaining the
434 * data backing this list and for producing a view to represent an
435 * item in that data set.
436 *
437 * @see #getAdapter()
438 */
439 @Override
440 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700441 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 mAdapter.unregisterDataSetObserver(mDataSetObserver);
443 }
444
445 resetList();
446 mRecycler.clear();
447
448 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
449 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
450 } else {
451 mAdapter = adapter;
452 }
453
454 mOldSelectedPosition = INVALID_POSITION;
455 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700456
457 // AbsListView#setAdapter will update choice mode states.
458 super.setAdapter(adapter);
459
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 if (mAdapter != null) {
461 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
462 mOldItemCount = mItemCount;
463 mItemCount = mAdapter.getCount();
464 checkFocus();
465
466 mDataSetObserver = new AdapterDataSetObserver();
467 mAdapter.registerDataSetObserver(mDataSetObserver);
468
469 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
470
471 int position;
472 if (mStackFromBottom) {
473 position = lookForSelectablePosition(mItemCount - 1, false);
474 } else {
475 position = lookForSelectablePosition(0, true);
476 }
477 setSelectedPositionInt(position);
478 setNextSelectedPositionInt(position);
479
480 if (mItemCount == 0) {
481 // Nothing selected
482 checkSelectionChanged();
483 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 } else {
485 mAreAllItemsSelectable = true;
486 checkFocus();
487 // Nothing selected
488 checkSelectionChanged();
489 }
490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 requestLayout();
492 }
493
494
495 /**
496 * The list is empty. Clear everything out.
497 */
498 @Override
499 void resetList() {
Romain Guy2e447d42009-04-28 18:01:24 -0700500 // The parent's resetList() will remove all views from the layout so we need to
501 // cleanup the state of our footers and headers
502 clearRecycledState(mHeaderViewInfos);
503 clearRecycledState(mFooterViewInfos);
504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 super.resetList();
Romain Guy2e447d42009-04-28 18:01:24 -0700506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 mLayoutMode = LAYOUT_NORMAL;
508 }
509
Romain Guy2e447d42009-04-28 18:01:24 -0700510 private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
511 if (infos != null) {
512 final int count = infos.size();
513
514 for (int i = 0; i < count; i++) {
515 final View child = infos.get(i).view;
516 final LayoutParams p = (LayoutParams) child.getLayoutParams();
517 if (p != null) {
518 p.recycledHeaderFooter = false;
519 }
520 }
521 }
522 }
523
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 /**
525 * @return Whether the list needs to show the top fading edge
526 */
527 private boolean showingTopFadingEdge() {
528 final int listTop = mScrollY + mListPadding.top;
529 return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
530 }
531
532 /**
533 * @return Whether the list needs to show the bottom fading edge
534 */
535 private boolean showingBottomFadingEdge() {
536 final int childCount = getChildCount();
537 final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
538 final int lastVisiblePosition = mFirstPosition + childCount - 1;
539
540 final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
541
542 return (lastVisiblePosition < mItemCount - 1)
543 || (bottomOfBottomChild < listBottom);
544 }
545
546
547 @Override
548 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
549
550 int rectTopWithinChild = rect.top;
551
552 // offset so rect is in coordinates of the this view
553 rect.offset(child.getLeft(), child.getTop());
554 rect.offset(-child.getScrollX(), -child.getScrollY());
555
556 final int height = getHeight();
557 int listUnfadedTop = getScrollY();
558 int listUnfadedBottom = listUnfadedTop + height;
559 final int fadingEdge = getVerticalFadingEdgeLength();
560
561 if (showingTopFadingEdge()) {
562 // leave room for top fading edge as long as rect isn't at very top
563 if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
564 listUnfadedTop += fadingEdge;
565 }
566 }
567
568 int childCount = getChildCount();
569 int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
570
571 if (showingBottomFadingEdge()) {
572 // leave room for bottom fading edge as long as rect isn't at very bottom
573 if ((mSelectedPosition < mItemCount - 1)
574 || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
575 listUnfadedBottom -= fadingEdge;
576 }
577 }
578
579 int scrollYDelta = 0;
580
581 if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
582 // need to MOVE DOWN to get it in view: move down just enough so
583 // that the entire rectangle is in view (or at least the first
584 // screen size chunk).
585
586 if (rect.height() > height) {
587 // just enough to get screen size chunk on
588 scrollYDelta += (rect.top - listUnfadedTop);
589 } else {
590 // get entire rect at bottom of screen
591 scrollYDelta += (rect.bottom - listUnfadedBottom);
592 }
593
594 // make sure we aren't scrolling beyond the end of our children
595 int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
596 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
597 } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
598 // need to MOVE UP to get it in view: move up just enough so that
599 // entire rectangle is in view (or at least the first screen
600 // size chunk of it).
601
602 if (rect.height() > height) {
603 // screen size chunk
604 scrollYDelta -= (listUnfadedBottom - rect.bottom);
605 } else {
606 // entire rect at top
607 scrollYDelta -= (listUnfadedTop - rect.top);
608 }
609
610 // make sure we aren't scrolling any further than the top our children
611 int top = getChildAt(0).getTop();
612 int deltaToTop = top - listUnfadedTop;
613 scrollYDelta = Math.max(scrollYDelta, deltaToTop);
614 }
615
616 final boolean scroll = scrollYDelta != 0;
617 if (scroll) {
618 scrollListItemsBy(-scrollYDelta);
Dianne Hackborn079e2352010-10-18 17:02:43 -0700619 positionSelector(INVALID_POSITION, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 mSelectedTop = child.getTop();
621 invalidate();
622 }
623 return scroll;
624 }
625
626 /**
627 * {@inheritDoc}
628 */
629 @Override
630 void fillGap(boolean down) {
631 final int count = getChildCount();
632 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800633 int paddingTop = 0;
634 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
635 paddingTop = getListPaddingTop();
636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800637 final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800638 paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 fillDown(mFirstPosition + count, startOffset);
640 correctTooHigh(getChildCount());
641 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800642 int paddingBottom = 0;
643 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
644 paddingBottom = getListPaddingBottom();
645 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800647 getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800648 fillUp(mFirstPosition - 1, startOffset);
649 correctTooLow(getChildCount());
650 }
651 }
652
653 /**
654 * Fills the list from pos down to the end of the list view.
655 *
656 * @param pos The first position to put in the list
657 *
658 * @param nextTop The location where the top of the item associated with pos
659 * should be drawn
660 *
661 * @return The view that is currently selected, if it happens to be in the
662 * range that we draw.
663 */
664 private View fillDown(int pos, int nextTop) {
665 View selectedView = null;
666
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800667 int end = (mBottom - mTop);
668 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
669 end -= mListPadding.bottom;
670 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671
672 while (nextTop < end && pos < mItemCount) {
673 // is this the selected item?
674 boolean selected = pos == mSelectedPosition;
675 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
676
677 nextTop = child.getBottom() + mDividerHeight;
678 if (selected) {
679 selectedView = child;
680 }
681 pos++;
682 }
683
Adam Cohenb9673922012-01-05 13:58:47 -0800684 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685 return selectedView;
686 }
687
688 /**
689 * Fills the list from pos up to the top of the list view.
690 *
691 * @param pos The first position to put in the list
692 *
693 * @param nextBottom The location where the bottom of the item associated
694 * with pos should be drawn
695 *
696 * @return The view that is currently selected
697 */
698 private View fillUp(int pos, int nextBottom) {
699 View selectedView = null;
700
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800701 int end = 0;
702 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
703 end = mListPadding.top;
704 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705
706 while (nextBottom > end && pos >= 0) {
707 // is this the selected item?
708 boolean selected = pos == mSelectedPosition;
709 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
710 nextBottom = child.getTop() - mDividerHeight;
711 if (selected) {
712 selectedView = child;
713 }
714 pos--;
715 }
716
717 mFirstPosition = pos + 1;
Adam Cohenb9673922012-01-05 13:58:47 -0800718 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 return selectedView;
720 }
721
722 /**
723 * Fills the list from top to bottom, starting with mFirstPosition
724 *
725 * @param nextTop The location where the top of the first item should be
726 * drawn
727 *
728 * @return The view that is currently selected
729 */
730 private View fillFromTop(int nextTop) {
731 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
732 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
733 if (mFirstPosition < 0) {
734 mFirstPosition = 0;
735 }
736 return fillDown(mFirstPosition, nextTop);
737 }
738
739
740 /**
741 * Put mSelectedPosition in the middle of the screen and then build up and
742 * down from there. This method forces mSelectedPosition to the center.
743 *
744 * @param childrenTop Top of the area in which children can be drawn, as
745 * measured in pixels
746 * @param childrenBottom Bottom of the area in which children can be drawn,
747 * as measured in pixels
748 * @return Currently selected view
749 */
750 private View fillFromMiddle(int childrenTop, int childrenBottom) {
751 int height = childrenBottom - childrenTop;
752
753 int position = reconcileSelectedPosition();
754
755 View sel = makeAndAddView(position, childrenTop, true,
756 mListPadding.left, true);
757 mFirstPosition = position;
758
759 int selHeight = sel.getMeasuredHeight();
760 if (selHeight <= height) {
761 sel.offsetTopAndBottom((height - selHeight) / 2);
762 }
763
764 fillAboveAndBelow(sel, position);
765
766 if (!mStackFromBottom) {
767 correctTooHigh(getChildCount());
768 } else {
769 correctTooLow(getChildCount());
770 }
771
772 return sel;
773 }
774
775 /**
776 * Once the selected view as been placed, fill up the visible area above and
777 * below it.
778 *
779 * @param sel The selected view
780 * @param position The position corresponding to sel
781 */
782 private void fillAboveAndBelow(View sel, int position) {
783 final int dividerHeight = mDividerHeight;
784 if (!mStackFromBottom) {
785 fillUp(position - 1, sel.getTop() - dividerHeight);
786 adjustViewsUpOrDown();
787 fillDown(position + 1, sel.getBottom() + dividerHeight);
788 } else {
789 fillDown(position + 1, sel.getBottom() + dividerHeight);
790 adjustViewsUpOrDown();
791 fillUp(position - 1, sel.getTop() - dividerHeight);
792 }
793 }
794
795
796 /**
797 * Fills the grid based on positioning the new selection at a specific
798 * location. The selection may be moved so that it does not intersect the
799 * faded edges. The grid is then filled upwards and downwards from there.
800 *
801 * @param selectedTop Where the selected item should be
802 * @param childrenTop Where to start drawing children
803 * @param childrenBottom Last pixel where children can be drawn
804 * @return The view that currently has selection
805 */
806 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
807 int fadingEdgeLength = getVerticalFadingEdgeLength();
808 final int selectedPosition = mSelectedPosition;
809
810 View sel;
811
812 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
813 selectedPosition);
814 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
815 selectedPosition);
816
817 sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
818
819
820 // Some of the newly selected item extends below the bottom of the list
821 if (sel.getBottom() > bottomSelectionPixel) {
822 // Find space available above the selection into which we can scroll
823 // upwards
824 final int spaceAbove = sel.getTop() - topSelectionPixel;
825
826 // Find space required to bring the bottom of the selected item
827 // fully into view
828 final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
829 final int offset = Math.min(spaceAbove, spaceBelow);
830
831 // Now offset the selected item to get it into view
832 sel.offsetTopAndBottom(-offset);
833 } else if (sel.getTop() < topSelectionPixel) {
834 // Find space required to bring the top of the selected item fully
835 // into view
836 final int spaceAbove = topSelectionPixel - sel.getTop();
837
838 // Find space available below the selection into which we can scroll
839 // downwards
840 final int spaceBelow = bottomSelectionPixel - sel.getBottom();
841 final int offset = Math.min(spaceAbove, spaceBelow);
842
843 // Offset the selected item to get it into view
844 sel.offsetTopAndBottom(offset);
845 }
846
847 // Fill in views above and below
848 fillAboveAndBelow(sel, selectedPosition);
849
850 if (!mStackFromBottom) {
851 correctTooHigh(getChildCount());
852 } else {
853 correctTooLow(getChildCount());
854 }
855
856 return sel;
857 }
858
859 /**
860 * Calculate the bottom-most pixel we can draw the selection into
861 *
862 * @param childrenBottom Bottom pixel were children can be drawn
863 * @param fadingEdgeLength Length of the fading edge in pixels, if present
864 * @param selectedPosition The position that will be selected
865 * @return The bottom-most pixel we can draw the selection into
866 */
867 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
868 int selectedPosition) {
869 int bottomSelectionPixel = childrenBottom;
870 if (selectedPosition != mItemCount - 1) {
871 bottomSelectionPixel -= fadingEdgeLength;
872 }
873 return bottomSelectionPixel;
874 }
875
876 /**
877 * Calculate the top-most pixel we can draw the selection into
878 *
879 * @param childrenTop Top pixel were children can be drawn
880 * @param fadingEdgeLength Length of the fading edge in pixels, if present
881 * @param selectedPosition The position that will be selected
882 * @return The top-most pixel we can draw the selection into
883 */
884 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
885 // first pixel we can draw the selection into
886 int topSelectionPixel = childrenTop;
887 if (selectedPosition > 0) {
888 topSelectionPixel += fadingEdgeLength;
889 }
890 return topSelectionPixel;
891 }
892
Winson Chung499cb9f2010-07-16 11:18:17 -0700893 /**
894 * Smoothly scroll to the specified adapter position. The view will
895 * scroll such that the indicated position is displayed.
896 * @param position Scroll to this adapter position.
897 */
898 @android.view.RemotableViewMethod
899 public void smoothScrollToPosition(int position) {
900 super.smoothScrollToPosition(position);
901 }
902
903 /**
904 * Smoothly scroll to the specified adapter position offset. The view will
905 * scroll such that the indicated position is displayed.
906 * @param offset The amount to offset from the adapter position to scroll to.
907 */
908 @android.view.RemotableViewMethod
909 public void smoothScrollByOffset(int offset) {
910 super.smoothScrollByOffset(offset);
911 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912
913 /**
914 * Fills the list based on positioning the new selection relative to the old
915 * selection. The new selection will be placed at, above, or below the
916 * location of the new selection depending on how the selection is moving.
917 * The selection will then be pinned to the visible part of the screen,
918 * excluding the edges that are faded. The list is then filled upwards and
919 * downwards from there.
920 *
921 * @param oldSel The old selected view. Useful for trying to put the new
922 * selection in the same place
923 * @param newSel The view that is to become selected. Useful for trying to
924 * put the new selection in the same place
925 * @param delta Which way we are moving
926 * @param childrenTop Where to start drawing children
927 * @param childrenBottom Last pixel where children can be drawn
928 * @return The view that currently has selection
929 */
930 private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
931 int childrenBottom) {
932 int fadingEdgeLength = getVerticalFadingEdgeLength();
933 final int selectedPosition = mSelectedPosition;
934
935 View sel;
936
937 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
938 selectedPosition);
939 final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
940 selectedPosition);
941
942 if (delta > 0) {
943 /*
944 * Case 1: Scrolling down.
945 */
946
947 /*
948 * Before After
949 * | | | |
950 * +-------+ +-------+
951 * | A | | A |
952 * | 1 | => +-------+
953 * +-------+ | B |
954 * | B | | 2 |
955 * +-------+ +-------+
956 * | | | |
957 *
958 * Try to keep the top of the previously selected item where it was.
959 * oldSel = A
960 * sel = B
961 */
962
963 // Put oldSel (A) where it belongs
964 oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
965 mListPadding.left, false);
966
967 final int dividerHeight = mDividerHeight;
968
969 // Now put the new selection (B) below that
970 sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
971 mListPadding.left, true);
972
973 // Some of the newly selected item extends below the bottom of the list
974 if (sel.getBottom() > bottomSelectionPixel) {
975
976 // Find space available above the selection into which we can scroll upwards
977 int spaceAbove = sel.getTop() - topSelectionPixel;
978
979 // Find space required to bring the bottom of the selected item fully into view
980 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
981
982 // Don't scroll more than half the height of the list
983 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
984 int offset = Math.min(spaceAbove, spaceBelow);
985 offset = Math.min(offset, halfVerticalSpace);
986
987 // We placed oldSel, so offset that item
988 oldSel.offsetTopAndBottom(-offset);
989 // Now offset the selected item to get it into view
990 sel.offsetTopAndBottom(-offset);
991 }
992
993 // Fill in views above and below
994 if (!mStackFromBottom) {
995 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
996 adjustViewsUpOrDown();
997 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
998 } else {
999 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1000 adjustViewsUpOrDown();
1001 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1002 }
1003 } else if (delta < 0) {
1004 /*
1005 * Case 2: Scrolling up.
1006 */
1007
1008 /*
1009 * Before After
1010 * | | | |
1011 * +-------+ +-------+
1012 * | A | | A |
1013 * +-------+ => | 1 |
1014 * | B | +-------+
1015 * | 2 | | B |
1016 * +-------+ +-------+
1017 * | | | |
1018 *
1019 * Try to keep the top of the item about to become selected where it was.
1020 * newSel = A
1021 * olSel = B
1022 */
1023
1024 if (newSel != null) {
1025 // Try to position the top of newSel (A) where it was before it was selected
1026 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1027 true);
1028 } else {
1029 // If (A) was not on screen and so did not have a view, position
1030 // it above the oldSel (B)
1031 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1032 true);
1033 }
1034
1035 // Some of the newly selected item extends above the top of the list
1036 if (sel.getTop() < topSelectionPixel) {
1037 // Find space required to bring the top of the selected item fully into view
1038 int spaceAbove = topSelectionPixel - sel.getTop();
1039
1040 // Find space available below the selection into which we can scroll downwards
1041 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1042
1043 // Don't scroll more than half the height of the list
1044 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1045 int offset = Math.min(spaceAbove, spaceBelow);
1046 offset = Math.min(offset, halfVerticalSpace);
1047
1048 // Offset the selected item to get it into view
1049 sel.offsetTopAndBottom(offset);
1050 }
1051
1052 // Fill in views above and below
1053 fillAboveAndBelow(sel, selectedPosition);
1054 } else {
1055
1056 int oldTop = oldSel.getTop();
1057
1058 /*
1059 * Case 3: Staying still
1060 */
1061 sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1062
1063 // We're staying still...
1064 if (oldTop < childrenTop) {
1065 // ... but the top of the old selection was off screen.
1066 // (This can happen if the data changes size out from under us)
1067 int newBottom = sel.getBottom();
1068 if (newBottom < childrenTop + 20) {
1069 // Not enough visible -- bring it onscreen
1070 sel.offsetTopAndBottom(childrenTop - sel.getTop());
1071 }
1072 }
1073
1074 // Fill in views above and below
1075 fillAboveAndBelow(sel, selectedPosition);
1076 }
1077
1078 return sel;
1079 }
1080
Adam Powell9bf3c122010-02-26 11:32:07 -08001081 private class FocusSelector implements Runnable {
1082 private int mPosition;
1083 private int mPositionTop;
1084
1085 public FocusSelector setup(int position, int top) {
1086 mPosition = position;
1087 mPositionTop = top;
1088 return this;
1089 }
1090
1091 public void run() {
1092 setSelectionFromTop(mPosition, mPositionTop);
1093 }
1094 }
1095
1096 @Override
1097 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1098 if (getChildCount() > 0) {
1099 View focusedChild = getFocusedChild();
1100 if (focusedChild != null) {
1101 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1102 final int childBottom = focusedChild.getBottom();
1103 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1104 final int top = focusedChild.getTop() - offset;
1105 if (mFocusSelector == null) {
1106 mFocusSelector = new FocusSelector();
1107 }
1108 post(mFocusSelector.setup(childPosition, top));
1109 }
1110 }
1111 super.onSizeChanged(w, h, oldw, oldh);
1112 }
1113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 @Override
1115 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1116 // Sets up mListPadding
1117 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1118
1119 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1120 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1121 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1122 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1123
1124 int childWidth = 0;
1125 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001126 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127
1128 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1129 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
1130 heightMode == MeasureSpec.UNSPECIFIED)) {
Romain Guy21875052010-01-06 18:48:08 -08001131 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132
1133 measureScrapChild(child, 0, widthMeasureSpec);
1134
1135 childWidth = child.getMeasuredWidth();
1136 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001137 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138
Romain Guy9c3184cc2010-02-25 17:32:54 -08001139 if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1140 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001141 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 }
1143 }
1144
1145 if (widthMode == MeasureSpec.UNSPECIFIED) {
1146 widthSize = mListPadding.left + mListPadding.right + childWidth +
1147 getVerticalScrollbarWidth();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001148 } else {
1149 widthSize |= (childState&MEASURED_STATE_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 }
1151
1152 if (heightMode == MeasureSpec.UNSPECIFIED) {
1153 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1154 getVerticalFadingEdgeLength() * 2;
1155 }
1156
1157 if (heightMode == MeasureSpec.AT_MOST) {
1158 // TODO: after first layout we should maybe start at the first visible position, not 0
1159 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1160 }
1161
Dianne Hackborn189ee182010-12-02 21:48:53 -08001162 setMeasuredDimension(widthSize , heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 mWidthMeasureSpec = widthMeasureSpec;
1164 }
1165
1166 private void measureScrapChild(View child, int position, int widthMeasureSpec) {
1167 LayoutParams p = (LayoutParams) child.getLayoutParams();
1168 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001169 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001170 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 }
1172 p.viewType = mAdapter.getItemViewType(position);
Romain Guy0bf88592010-03-02 13:38:44 -08001173 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174
1175 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
1176 mListPadding.left + mListPadding.right, p.width);
1177 int lpHeight = p.height;
1178 int childHeightSpec;
1179 if (lpHeight > 0) {
1180 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1181 } else {
1182 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1183 }
1184 child.measure(childWidthSpec, childHeightSpec);
1185 }
1186
1187 /**
1188 * @return True to recycle the views used to measure this ListView in
1189 * UNSPECIFIED/AT_MOST modes, false otherwise.
1190 * @hide
1191 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001192 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 protected boolean recycleOnMeasure() {
1194 return true;
1195 }
1196
1197 /**
1198 * Measures the height of the given range of children (inclusive) and
1199 * returns the height with this ListView's padding and divider heights
1200 * included. If maxHeight is provided, the measuring will stop when the
1201 * current height reaches maxHeight.
1202 *
1203 * @param widthMeasureSpec The width measure spec to be given to a child's
1204 * {@link View#measure(int, int)}.
1205 * @param startPosition The position of the first child to be shown.
1206 * @param endPosition The (inclusive) position of the last child to be
1207 * shown. Specify {@link #NO_POSITION} if the last child should be
1208 * the last available child from the adapter.
1209 * @param maxHeight The maximum height that will be returned (if all the
1210 * children don't fit in this value, this value will be
1211 * returned).
1212 * @param disallowPartialChildPosition In general, whether the returned
1213 * height should only contain entire children. This is more
1214 * powerful--it is the first inclusive position at which partial
1215 * children will not be allowed. Example: it looks nice to have
1216 * at least 3 completely visible children, and in portrait this
1217 * will most likely fit; but in landscape there could be times
1218 * when even 2 children can not be completely shown, so a value
1219 * of 2 (remember, inclusive) would be good (assuming
1220 * startPosition is 0).
1221 * @return The height of this ListView with the given children.
1222 */
1223 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
1224 final int maxHeight, int disallowPartialChildPosition) {
1225
1226 final ListAdapter adapter = mAdapter;
1227 if (adapter == null) {
1228 return mListPadding.top + mListPadding.bottom;
1229 }
1230
1231 // Include the padding of the list
1232 int returnedHeight = mListPadding.top + mListPadding.bottom;
1233 final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
1234 // The previous height value that was less than maxHeight and contained
1235 // no partial children
1236 int prevHeightWithoutPartialChild = 0;
1237 int i;
1238 View child;
1239
1240 // mItemCount - 1 since endPosition parameter is inclusive
1241 endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1242 final AbsListView.RecycleBin recycleBin = mRecycler;
1243 final boolean recyle = recycleOnMeasure();
Romain Guy21875052010-01-06 18:48:08 -08001244 final boolean[] isScrap = mIsScrap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245
1246 for (i = startPosition; i <= endPosition; ++i) {
Romain Guy21875052010-01-06 18:48:08 -08001247 child = obtainView(i, isScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248
1249 measureScrapChild(child, i, widthMeasureSpec);
1250
1251 if (i > 0) {
1252 // Count the divider for all but one child
1253 returnedHeight += dividerHeight;
1254 }
1255
1256 // Recycle the view before we possibly return from the method
Romain Guy9c3184cc2010-02-25 17:32:54 -08001257 if (recyle && recycleBin.shouldRecycleViewType(
1258 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001259 recycleBin.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 }
1261
1262 returnedHeight += child.getMeasuredHeight();
1263
1264 if (returnedHeight >= maxHeight) {
1265 // We went over, figure out which height to return. If returnedHeight > maxHeight,
1266 // then the i'th position did not fit completely.
1267 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1268 && (i > disallowPartialChildPosition) // We've past the min pos
1269 && (prevHeightWithoutPartialChild > 0) // We have a prev height
1270 && (returnedHeight != maxHeight) // i'th child did not fit completely
1271 ? prevHeightWithoutPartialChild
1272 : maxHeight;
1273 }
1274
1275 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1276 prevHeightWithoutPartialChild = returnedHeight;
1277 }
1278 }
1279
1280 // At this point, we went through the range of children, and they each
1281 // completely fit, so return the returnedHeight
1282 return returnedHeight;
1283 }
1284
1285 @Override
1286 int findMotionRow(int y) {
1287 int childCount = getChildCount();
1288 if (childCount > 0) {
Adam Powell84222e02010-03-11 13:42:57 -08001289 if (!mStackFromBottom) {
1290 for (int i = 0; i < childCount; i++) {
1291 View v = getChildAt(i);
1292 if (y <= v.getBottom()) {
1293 return mFirstPosition + i;
1294 }
1295 }
1296 } else {
1297 for (int i = childCount - 1; i >= 0; i--) {
1298 View v = getChildAt(i);
1299 if (y >= v.getTop()) {
1300 return mFirstPosition + i;
1301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 }
1303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 }
1305 return INVALID_POSITION;
1306 }
1307
1308 /**
1309 * Put a specific item at a specific location on the screen and then build
1310 * up and down from there.
1311 *
1312 * @param position The reference view to use as the starting point
1313 * @param top Pixel offset from the top of this view to the top of the
1314 * reference view.
1315 *
1316 * @return The selected view, or null if the selected view is outside the
1317 * visible area.
1318 */
1319 private View fillSpecific(int position, int top) {
1320 boolean tempIsSelected = position == mSelectedPosition;
1321 View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1322 // Possibly changed again in fillUp if we add rows above this one.
1323 mFirstPosition = position;
1324
1325 View above;
1326 View below;
1327
1328 final int dividerHeight = mDividerHeight;
1329 if (!mStackFromBottom) {
1330 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1331 // This will correct for the top of the first view not touching the top of the list
1332 adjustViewsUpOrDown();
1333 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1334 int childCount = getChildCount();
1335 if (childCount > 0) {
1336 correctTooHigh(childCount);
1337 }
1338 } else {
1339 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1340 // This will correct for the bottom of the last view not touching the bottom of the list
1341 adjustViewsUpOrDown();
1342 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1343 int childCount = getChildCount();
1344 if (childCount > 0) {
1345 correctTooLow(childCount);
1346 }
1347 }
1348
1349 if (tempIsSelected) {
1350 return temp;
1351 } else if (above != null) {
1352 return above;
1353 } else {
1354 return below;
1355 }
1356 }
1357
1358 /**
1359 * Check if we have dragged the bottom of the list too high (we have pushed the
1360 * top element off the top of the screen when we did not need to). Correct by sliding
1361 * everything back down.
1362 *
1363 * @param childCount Number of children
1364 */
1365 private void correctTooHigh(int childCount) {
1366 // First see if the last item is visible. If it is not, it is OK for the
1367 // top of the list to be pushed up.
1368 int lastPosition = mFirstPosition + childCount - 1;
1369 if (lastPosition == mItemCount - 1 && childCount > 0) {
1370
1371 // Get the last child ...
1372 final View lastChild = getChildAt(childCount - 1);
1373
1374 // ... and its bottom edge
1375 final int lastBottom = lastChild.getBottom();
1376
1377 // This is bottom of our drawable area
1378 final int end = (mBottom - mTop) - mListPadding.bottom;
1379
1380 // This is how far the bottom edge of the last view is from the bottom of the
1381 // drawable area
1382 int bottomOffset = end - lastBottom;
1383 View firstChild = getChildAt(0);
1384 final int firstTop = firstChild.getTop();
1385
1386 // Make sure we are 1) Too high, and 2) Either there are more rows above the
1387 // first row or the first row is scrolled off the top of the drawable area
1388 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
1389 if (mFirstPosition == 0) {
1390 // Don't pull the top too far down
1391 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1392 }
1393 // Move everything down
1394 offsetChildrenTopAndBottom(bottomOffset);
1395 if (mFirstPosition > 0) {
1396 // Fill the gap that was opened above mFirstPosition with more rows, if
1397 // possible
1398 fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1399 // Close up the remaining gap
1400 adjustViewsUpOrDown();
1401 }
1402
1403 }
1404 }
1405 }
1406
1407 /**
1408 * Check if we have dragged the bottom of the list too low (we have pushed the
1409 * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1410 * everything back up.
1411 *
1412 * @param childCount Number of children
1413 */
1414 private void correctTooLow(int childCount) {
1415 // First see if the first item is visible. If it is not, it is OK for the
1416 // bottom of the list to be pushed down.
1417 if (mFirstPosition == 0 && childCount > 0) {
1418
1419 // Get the first child ...
1420 final View firstChild = getChildAt(0);
1421
1422 // ... and its top edge
1423 final int firstTop = firstChild.getTop();
1424
1425 // This is top of our drawable area
1426 final int start = mListPadding.top;
1427
1428 // This is bottom of our drawable area
1429 final int end = (mBottom - mTop) - mListPadding.bottom;
1430
1431 // This is how far the top edge of the first view is from the top of the
1432 // drawable area
1433 int topOffset = firstTop - start;
1434 View lastChild = getChildAt(childCount - 1);
1435 final int lastBottom = lastChild.getBottom();
1436 int lastPosition = mFirstPosition + childCount - 1;
1437
1438 // Make sure we are 1) Too low, and 2) Either there are more rows below the
1439 // last row or the last row is scrolled off the bottom of the drawable area
Romain Guy6198ae82009-08-31 17:45:55 -07001440 if (topOffset > 0) {
1441 if (lastPosition < mItemCount - 1 || lastBottom > end) {
1442 if (lastPosition == mItemCount - 1) {
1443 // Don't pull the bottom too far up
1444 topOffset = Math.min(topOffset, lastBottom - end);
1445 }
1446 // Move everything up
1447 offsetChildrenTopAndBottom(-topOffset);
1448 if (lastPosition < mItemCount - 1) {
1449 // Fill the gap that was opened below the last position with more rows, if
1450 // possible
1451 fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1452 // Close up the remaining gap
1453 adjustViewsUpOrDown();
1454 }
1455 } else if (lastPosition == mItemCount - 1) {
1456 adjustViewsUpOrDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457 }
1458 }
1459 }
1460 }
1461
1462 @Override
1463 protected void layoutChildren() {
1464 final boolean blockLayoutRequests = mBlockLayoutRequests;
1465 if (!blockLayoutRequests) {
1466 mBlockLayoutRequests = true;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001467 } else {
1468 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 }
1470
1471 try {
1472 super.layoutChildren();
1473
1474 invalidate();
1475
1476 if (mAdapter == null) {
1477 resetList();
1478 invokeOnItemScrollListener();
1479 return;
1480 }
1481
1482 int childrenTop = mListPadding.top;
1483 int childrenBottom = mBottom - mTop - mListPadding.bottom;
1484
1485 int childCount = getChildCount();
Romain Guyead0d4d2009-12-08 17:33:53 -08001486 int index = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001487 int delta = 0;
1488
1489 View sel;
1490 View oldSel = null;
1491 View oldFirst = null;
1492 View newSel = null;
1493
1494 View focusLayoutRestoreView = null;
1495
alanv30ee76c2012-09-07 10:31:16 -07001496 AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
1497 View accessibilityFocusLayoutRestoreView = null;
1498 int accessibilityFocusPosition = INVALID_POSITION;
1499
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 // Remember stuff we will need down below
1501 switch (mLayoutMode) {
1502 case LAYOUT_SET_SELECTION:
1503 index = mNextSelectedPosition - mFirstPosition;
1504 if (index >= 0 && index < childCount) {
1505 newSel = getChildAt(index);
1506 }
1507 break;
1508 case LAYOUT_FORCE_TOP:
1509 case LAYOUT_FORCE_BOTTOM:
1510 case LAYOUT_SPECIFIC:
1511 case LAYOUT_SYNC:
1512 break;
1513 case LAYOUT_MOVE_SELECTION:
1514 default:
1515 // Remember the previously selected view
1516 index = mSelectedPosition - mFirstPosition;
1517 if (index >= 0 && index < childCount) {
1518 oldSel = getChildAt(index);
1519 }
1520
1521 // Remember the previous first child
1522 oldFirst = getChildAt(0);
1523
1524 if (mNextSelectedPosition >= 0) {
1525 delta = mNextSelectedPosition - mSelectedPosition;
1526 }
1527
1528 // Caution: newSel might be null
1529 newSel = getChildAt(index + delta);
1530 }
1531
1532
1533 boolean dataChanged = mDataChanged;
1534 if (dataChanged) {
1535 handleDataChanged();
1536 }
1537
1538 // Handle the empty set by removing all views that are visible
1539 // and calling it a day
1540 if (mItemCount == 0) {
1541 resetList();
1542 invokeOnItemScrollListener();
1543 return;
Romain Guyb45f1242009-03-24 21:30:00 -07001544 } else if (mItemCount != mAdapter.getCount()) {
1545 throw new IllegalStateException("The content of the adapter has changed but "
1546 + "ListView did not receive a notification. Make sure the content of "
1547 + "your adapter is not modified from a background thread, but only "
Owen Lin3940f2d2009-08-13 15:21:16 +08001548 + "from the UI thread. [in ListView(" + getId() + ", " + getClass()
1549 + ") with Adapter(" + mAdapter.getClass() + ")]");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 }
1551
1552 setSelectedPositionInt(mNextSelectedPosition);
1553
Alan Viveretteb53c5f62013-03-15 15:50:37 -07001554 // Remember which child, if any, had accessibility focus. This must
1555 // occur before recycling any views, since that will clear
1556 // accessibility focus.
1557 final ViewRootImpl viewRootImpl = getViewRootImpl();
1558 if (viewRootImpl != null) {
1559 final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1560 if (accessFocusedView != null) {
1561 final View accessFocusedChild = findAccessibilityFocusedChild(
1562 accessFocusedView);
1563 if (accessFocusedChild != null) {
1564 if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) {
1565 // If the views won't be changing, try to maintain
1566 // focus on the current view host and (if
1567 // applicable) its virtual view.
1568 accessibilityFocusLayoutRestoreView = accessFocusedView;
1569 accessibilityFocusLayoutRestoreNode = viewRootImpl
1570 .getAccessibilityFocusedVirtualView();
1571 } else {
1572 // Otherwise, try to maintain focus at the same
1573 // position.
1574 accessibilityFocusPosition = getPositionForView(accessFocusedChild);
1575 }
1576 }
1577 }
1578 }
1579
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001580 // Pull all children into the RecycleBin.
1581 // These views will be reused if possible
1582 final int firstPosition = mFirstPosition;
1583 final RecycleBin recycleBin = mRecycler;
1584
1585 // reset the focus restoration
1586 View focusLayoutRestoreDirectChild = null;
1587
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001588 // Don't put header or footer views into the Recycler. Those are
1589 // already cached in mHeaderViews;
1590 if (dataChanged) {
1591 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001592 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001593 }
1594 } else {
1595 recycleBin.fillActiveViews(childCount, firstPosition);
1596 }
1597
1598 // take focus back to us temporarily to avoid the eventual
1599 // call to clear focus when removing the focused child below
Joe Onoratoc6cc0f82011-04-12 11:53:13 -07001600 // from messing things up when ViewAncestor assigns focus back
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001601 // to someone else
1602 final View focusedChild = getFocusedChild();
1603 if (focusedChild != null) {
1604 // TODO: in some cases focusedChild.getParent() == null
1605
1606 // we can remember the focused view to restore after relayout if the
1607 // data hasn't changed, or if the focused position is a header or footer
1608 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001609 focusLayoutRestoreDirectChild = focusedChild;
1610 // remember the specific view that had focus
1611 focusLayoutRestoreView = findFocus();
1612 if (focusLayoutRestoreView != null) {
1613 // tell it we are going to mess with it
1614 focusLayoutRestoreView.onStartTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001615 }
1616 }
1617 requestFocus();
1618 }
1619
1620 // Clear out old views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001621 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001622 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001623
1624 switch (mLayoutMode) {
1625 case LAYOUT_SET_SELECTION:
1626 if (newSel != null) {
1627 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1628 } else {
1629 sel = fillFromMiddle(childrenTop, childrenBottom);
1630 }
1631 break;
1632 case LAYOUT_SYNC:
1633 sel = fillSpecific(mSyncPosition, mSpecificTop);
1634 break;
1635 case LAYOUT_FORCE_BOTTOM:
1636 sel = fillUp(mItemCount - 1, childrenBottom);
1637 adjustViewsUpOrDown();
1638 break;
1639 case LAYOUT_FORCE_TOP:
1640 mFirstPosition = 0;
1641 sel = fillFromTop(childrenTop);
1642 adjustViewsUpOrDown();
1643 break;
1644 case LAYOUT_SPECIFIC:
1645 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
1646 break;
1647 case LAYOUT_MOVE_SELECTION:
1648 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1649 break;
1650 default:
1651 if (childCount == 0) {
1652 if (!mStackFromBottom) {
1653 final int position = lookForSelectablePosition(0, true);
1654 setSelectedPositionInt(position);
1655 sel = fillFromTop(childrenTop);
1656 } else {
1657 final int position = lookForSelectablePosition(mItemCount - 1, false);
1658 setSelectedPositionInt(position);
1659 sel = fillUp(mItemCount - 1, childrenBottom);
1660 }
1661 } else {
1662 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1663 sel = fillSpecific(mSelectedPosition,
1664 oldSel == null ? childrenTop : oldSel.getTop());
1665 } else if (mFirstPosition < mItemCount) {
1666 sel = fillSpecific(mFirstPosition,
1667 oldFirst == null ? childrenTop : oldFirst.getTop());
1668 } else {
1669 sel = fillSpecific(0, childrenTop);
1670 }
1671 }
1672 break;
1673 }
1674
1675 // Flush any cached views that did not get reused above
1676 recycleBin.scrapActiveViews();
1677
1678 if (sel != null) {
Romain Guy3616a412009-09-15 13:50:37 -07001679 // the current selected item should get focus if items
1680 // are focusable
1681 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1682 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
Adam Powell2d8b0172012-02-01 17:23:41 -08001683 focusLayoutRestoreView != null &&
Romain Guy3616a412009-09-15 13:50:37 -07001684 focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1685 if (!focusWasTaken) {
1686 // selected item didn't take focus, fine, but still want
1687 // to make sure something else outside of the selected view
1688 // has focus
1689 final View focused = getFocusedChild();
1690 if (focused != null) {
1691 focused.clearFocus();
1692 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001693 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001694 } else {
1695 sel.setSelected(false);
1696 mSelectorRect.setEmpty();
1697 }
1698 } else {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001699 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001700 }
1701 mSelectedTop = sel.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001702 } else {
Romain Guy3616a412009-09-15 13:50:37 -07001703 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1704 View child = getChildAt(mMotionPosition - mFirstPosition);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001705 if (child != null) positionSelector(mMotionPosition, child);
Romain Guy3616a412009-09-15 13:50:37 -07001706 } else {
1707 mSelectedTop = 0;
1708 mSelectorRect.setEmpty();
1709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710
Romain Guy3616a412009-09-15 13:50:37 -07001711 // even if there is not selected position, we may need to restore
1712 // focus (i.e. something focusable in touch mode)
1713 if (hasFocus() && focusLayoutRestoreView != null) {
1714 focusLayoutRestoreView.requestFocus();
1715 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001716 }
1717
alanv30ee76c2012-09-07 10:31:16 -07001718 // Attempt to restore accessibility focus.
Alan Viverette11f796e2013-04-16 14:32:17 -07001719 if (accessibilityFocusLayoutRestoreView != null) {
1720 final AccessibilityNodeProvider provider =
1721 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1722 if ((accessibilityFocusLayoutRestoreNode != null) && (provider != null)) {
1723 final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1724 accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1725 provider.performAction(virtualViewId,
1726 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1727 } else {
1728 accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1729 }
alanv30ee76c2012-09-07 10:31:16 -07001730 } else if (accessibilityFocusPosition != INVALID_POSITION) {
1731 // Bound the position within the visible children.
1732 final int position = MathUtils.constrain(
1733 (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1));
1734 final View restoreView = getChildAt(position);
1735 if (restoreView != null) {
1736 restoreView.requestAccessibilityFocus();
1737 }
1738 }
1739
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740 // tell focus view we are done mucking with it, if it is still in
1741 // our view hierarchy.
1742 if (focusLayoutRestoreView != null
1743 && focusLayoutRestoreView.getWindowToken() != null) {
1744 focusLayoutRestoreView.onFinishTemporaryDetach();
1745 }
1746
1747 mLayoutMode = LAYOUT_NORMAL;
1748 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001749 if (mPositionScrollAfterLayout != null) {
1750 post(mPositionScrollAfterLayout);
1751 mPositionScrollAfterLayout = null;
1752 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001753 mNeedSync = false;
1754 setNextSelectedPositionInt(mSelectedPosition);
1755
1756 updateScrollIndicators();
1757
1758 if (mItemCount > 0) {
1759 checkSelectionChanged();
1760 }
1761
1762 invokeOnItemScrollListener();
1763 } finally {
1764 if (!blockLayoutRequests) {
1765 mBlockLayoutRequests = false;
1766 }
1767 }
1768 }
1769
1770 /**
alanv30ee76c2012-09-07 10:31:16 -07001771 * @param focusedView the view that has accessibility focus.
1772 * @return the direct child that contains accessibility focus.
1773 */
1774 private View findAccessibilityFocusedChild(View focusedView) {
1775 ViewParent viewParent = focusedView.getParent();
1776 while ((viewParent instanceof View) && (viewParent != this)) {
1777 focusedView = (View) viewParent;
1778 viewParent = viewParent.getParent();
1779 }
1780 if (!(viewParent instanceof View)) {
1781 return null;
1782 }
1783 return focusedView;
1784 }
1785
1786 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 * @param child a direct child of this list.
1788 * @return Whether child is a header or footer view.
1789 */
1790 private boolean isDirectChildHeaderOrFooter(View child) {
1791
1792 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1793 final int numHeaders = headers.size();
1794 for (int i = 0; i < numHeaders; i++) {
1795 if (child == headers.get(i).view) {
1796 return true;
1797 }
1798 }
1799 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1800 final int numFooters = footers.size();
1801 for (int i = 0; i < numFooters; i++) {
1802 if (child == footers.get(i).view) {
1803 return true;
1804 }
1805 }
1806 return false;
1807 }
1808
1809 /**
1810 * Obtain the view and add it to our list of children. The view can be made
1811 * fresh, converted from an unused view, or used as is if it was in the
1812 * recycle bin.
1813 *
1814 * @param position Logical position in the list
1815 * @param y Top or bottom edge of the view to add
1816 * @param flow If flow is true, align top edge to y. If false, align bottom
1817 * edge to y.
1818 * @param childrenLeft Left edge where children should be positioned
1819 * @param selected Is this position selected?
1820 * @return View that was added
1821 */
1822 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1823 boolean selected) {
1824 View child;
1825
1826
1827 if (!mDataChanged) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001828 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001829 child = mRecycler.getActiveView(position);
1830 if (child != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001831 // Found it -- we're using an existing child
1832 // This just needs to be positioned
1833 setupChild(child, position, y, flow, childrenLeft, selected, true);
1834
1835 return child;
1836 }
1837 }
1838
1839 // Make a new view for this position, or convert an unused view if possible
Romain Guy21875052010-01-06 18:48:08 -08001840 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001841
1842 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001843 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001844
1845 return child;
1846 }
1847
1848 /**
1849 * Add a view as a child and make sure it is measured (if necessary) and
1850 * positioned properly.
1851 *
1852 * @param child The view to add
1853 * @param position The position of this child
1854 * @param y The y position relative to which this view will be positioned
1855 * @param flowDown If true, align top edge to y. If false, align bottom
1856 * edge to y.
1857 * @param childrenLeft Left edge where children should be positioned
1858 * @param selected Is this position selected?
1859 * @param recycled Has this view been pulled from the recycle bin? If so it
1860 * does not need to be remeasured.
1861 */
1862 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1863 boolean selected, boolean recycled) {
1864 final boolean isSelected = selected && shouldShowSelector();
1865 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001866 final int mode = mTouchMode;
1867 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1868 mMotionPosition == position;
1869 final boolean updateChildPressed = isPressed != child.isPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001870 final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1871
1872 // Respect layout params that are already in the view. Otherwise make some up...
1873 // noinspection unchecked
1874 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1875 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001876 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001877 }
1878 p.viewType = mAdapter.getItemViewType(position);
1879
Romain Guy0bf88592010-03-02 13:38:44 -08001880 if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001881 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001882 attachViewToParent(child, flowDown ? -1 : 0, p);
1883 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001884 p.forceAdd = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001885 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1886 p.recycledHeaderFooter = true;
1887 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001888 addViewInLayout(child, flowDown ? -1 : 0, p, true);
1889 }
1890
1891 if (updateChildSelected) {
1892 child.setSelected(isSelected);
1893 }
1894
Romain Guy3616a412009-09-15 13:50:37 -07001895 if (updateChildPressed) {
1896 child.setPressed(isPressed);
1897 }
1898
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001899 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1900 if (child instanceof Checkable) {
1901 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001902 } else if (getContext().getApplicationInfo().targetSdkVersion
1903 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1904 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001905 }
1906 }
1907
1908 if (needToMeasure) {
1909 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
1910 mListPadding.left + mListPadding.right, p.width);
1911 int lpHeight = p.height;
1912 int childHeightSpec;
1913 if (lpHeight > 0) {
1914 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1915 } else {
1916 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1917 }
1918 child.measure(childWidthSpec, childHeightSpec);
1919 } else {
1920 cleanupLayoutState(child);
1921 }
1922
1923 final int w = child.getMeasuredWidth();
1924 final int h = child.getMeasuredHeight();
1925 final int childTop = flowDown ? y : y - h;
1926
1927 if (needToMeasure) {
1928 final int childRight = childrenLeft + w;
1929 final int childBottom = childTop + h;
1930 child.layout(childrenLeft, childTop, childRight, childBottom);
1931 } else {
1932 child.offsetLeftAndRight(childrenLeft - child.getLeft());
1933 child.offsetTopAndBottom(childTop - child.getTop());
1934 }
1935
1936 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1937 child.setDrawingCacheEnabled(true);
1938 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001939
1940 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1941 != position) {
1942 child.jumpDrawablesToCurrentState();
1943 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001944 }
1945
1946 @Override
1947 protected boolean canAnimate() {
1948 return super.canAnimate() && mItemCount > 0;
1949 }
1950
1951 /**
1952 * Sets the currently selected item. If in touch mode, the item will not be selected
1953 * but it will still be positioned appropriately. If the specified selection position
1954 * is less than 0, then the item at position 0 will be selected.
1955 *
1956 * @param position Index (starting at 0) of the data item to be selected.
1957 */
1958 @Override
1959 public void setSelection(int position) {
1960 setSelectionFromTop(position, 0);
1961 }
1962
1963 /**
1964 * Sets the selected item and positions the selection y pixels from the top edge
1965 * of the ListView. (If in touch mode, the item will not be selected but it will
1966 * still be positioned appropriately.)
1967 *
1968 * @param position Index (starting at 0) of the data item to be selected.
1969 * @param y The distance from the top edge of the ListView (plus padding) that the
1970 * item will be positioned.
1971 */
1972 public void setSelectionFromTop(int position, int y) {
1973 if (mAdapter == null) {
1974 return;
1975 }
1976
1977 if (!isInTouchMode()) {
1978 position = lookForSelectablePosition(position, true);
1979 if (position >= 0) {
1980 setNextSelectedPositionInt(position);
1981 }
1982 } else {
1983 mResurrectToPosition = position;
1984 }
1985
1986 if (position >= 0) {
1987 mLayoutMode = LAYOUT_SPECIFIC;
1988 mSpecificTop = mListPadding.top + y;
1989
1990 if (mNeedSync) {
1991 mSyncPosition = position;
1992 mSyncRowId = mAdapter.getItemId(position);
1993 }
1994
Adam Powell1fa179ef2012-04-12 15:01:40 -07001995 if (mPositionScroller != null) {
1996 mPositionScroller.stop();
1997 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001998 requestLayout();
1999 }
2000 }
2001
2002 /**
2003 * Makes the item at the supplied position selected.
Mike Cleronf116bf82009-09-27 19:14:12 -07002004 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002005 * @param position the position of the item to select
2006 */
2007 @Override
2008 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002009 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07002010 boolean awakeScrollbars = false;
2011
2012 final int selectedPosition = mSelectedPosition;
2013
2014 if (selectedPosition >= 0) {
2015 if (position == selectedPosition - 1) {
2016 awakeScrollbars = true;
2017 } else if (position == selectedPosition + 1) {
2018 awakeScrollbars = true;
2019 }
2020 }
2021
Adam Powell1fa179ef2012-04-12 15:01:40 -07002022 if (mPositionScroller != null) {
2023 mPositionScroller.stop();
2024 }
2025
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002026 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07002027
2028 if (awakeScrollbars) {
2029 awakenScrollBars();
2030 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002031 }
2032
2033 /**
2034 * Find a position that can be selected (i.e., is not a separator).
2035 *
2036 * @param position The starting position to look at.
2037 * @param lookDown Whether to look down for other positions.
2038 * @return The next selectable position starting at position and then searching either up or
2039 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
2040 */
2041 @Override
2042 int lookForSelectablePosition(int position, boolean lookDown) {
2043 final ListAdapter adapter = mAdapter;
2044 if (adapter == null || isInTouchMode()) {
2045 return INVALID_POSITION;
2046 }
2047
2048 final int count = adapter.getCount();
2049 if (!mAreAllItemsSelectable) {
2050 if (lookDown) {
2051 position = Math.max(0, position);
2052 while (position < count && !adapter.isEnabled(position)) {
2053 position++;
2054 }
2055 } else {
2056 position = Math.min(position, count - 1);
2057 while (position >= 0 && !adapter.isEnabled(position)) {
2058 position--;
2059 }
2060 }
2061
2062 if (position < 0 || position >= count) {
2063 return INVALID_POSITION;
2064 }
2065 return position;
2066 } else {
2067 if (position < 0 || position >= count) {
2068 return INVALID_POSITION;
2069 }
2070 return position;
2071 }
2072 }
2073
2074 /**
2075 * setSelectionAfterHeaderView set the selection to be the first list item
2076 * after the header views.
2077 */
2078 public void setSelectionAfterHeaderView() {
2079 final int count = mHeaderViewInfos.size();
2080 if (count > 0) {
2081 mNextSelectedPosition = 0;
2082 return;
2083 }
2084
2085 if (mAdapter != null) {
2086 setSelection(count);
2087 } else {
2088 mNextSelectedPosition = count;
2089 mLayoutMode = LAYOUT_SET_SELECTION;
2090 }
2091
2092 }
2093
2094 @Override
2095 public boolean dispatchKeyEvent(KeyEvent event) {
2096 // Dispatch in the normal way
2097 boolean handled = super.dispatchKeyEvent(event);
2098 if (!handled) {
2099 // If we didn't handle it...
2100 View focused = getFocusedChild();
2101 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2102 // ... and our focused child didn't handle it
2103 // ... give it to ourselves so we can scroll if necessary
2104 handled = onKeyDown(event.getKeyCode(), event);
2105 }
2106 }
2107 return handled;
2108 }
2109
2110 @Override
2111 public boolean onKeyDown(int keyCode, KeyEvent event) {
2112 return commonKey(keyCode, 1, event);
2113 }
2114
2115 @Override
2116 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2117 return commonKey(keyCode, repeatCount, event);
2118 }
2119
2120 @Override
2121 public boolean onKeyUp(int keyCode, KeyEvent event) {
2122 return commonKey(keyCode, 1, event);
2123 }
2124
2125 private boolean commonKey(int keyCode, int count, KeyEvent event) {
Adam Powella2b986e2011-09-14 14:21:33 -07002126 if (mAdapter == null || !mIsAttached) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002127 return false;
2128 }
2129
2130 if (mDataChanged) {
2131 layoutChildren();
2132 }
2133
2134 boolean handled = false;
2135 int action = event.getAction();
2136
2137 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002138 switch (keyCode) {
2139 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002140 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002141 handled = resurrectSelectionIfNeeded();
2142 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002143 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002144 if (arrowScroll(FOCUS_UP)) {
2145 handled = true;
2146 } else {
2147 break;
2148 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002149 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002151 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002152 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002153 }
2154 break;
2155
2156 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002157 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002158 handled = resurrectSelectionIfNeeded();
2159 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002160 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002161 if (arrowScroll(FOCUS_DOWN)) {
2162 handled = true;
2163 } else {
2164 break;
2165 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002167 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002168 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002169 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002170 }
2171 break;
2172
2173 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002174 if (event.hasNoModifiers()) {
2175 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2176 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002177 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002178
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002179 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002180 if (event.hasNoModifiers()) {
2181 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2182 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002183 break;
2184
2185 case KeyEvent.KEYCODE_DPAD_CENTER:
2186 case KeyEvent.KEYCODE_ENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002187 if (event.hasNoModifiers()) {
2188 handled = resurrectSelectionIfNeeded();
2189 if (!handled
2190 && event.getRepeatCount() == 0 && getChildCount() > 0) {
2191 keyPressed();
2192 handled = true;
2193 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002195 break;
2196
2197 case KeyEvent.KEYCODE_SPACE:
2198 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002199 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002200 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002201 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002202 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002203 }
2204 handled = true;
2205 }
2206 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002207
2208 case KeyEvent.KEYCODE_PAGE_UP:
2209 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002210 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002211 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002212 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002213 }
2214 break;
2215
2216 case KeyEvent.KEYCODE_PAGE_DOWN:
2217 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002218 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002219 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002220 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002221 }
2222 break;
2223
2224 case KeyEvent.KEYCODE_MOVE_HOME:
2225 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002226 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002227 }
2228 break;
2229
2230 case KeyEvent.KEYCODE_MOVE_END:
2231 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002232 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002233 }
2234 break;
2235
2236 case KeyEvent.KEYCODE_TAB:
2237 // XXX Sometimes it is useful to be able to TAB through the items in
2238 // a ListView sequentially. Unfortunately this can create an
2239 // asymmetry in TAB navigation order unless the list selection
2240 // always reverts to the top or bottom when receiving TAB focus from
2241 // another widget. Leaving this behavior disabled for now but
2242 // perhaps it should be configurable (and more comprehensive).
2243 if (false) {
2244 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002245 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002246 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002247 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002248 }
2249 }
2250 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002251 }
2252 }
2253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002254 if (handled) {
2255 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002256 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002257
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002258 if (sendToTextFilter(keyCode, count, event)) {
2259 return true;
2260 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002261
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002262 switch (action) {
2263 case KeyEvent.ACTION_DOWN:
2264 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002265
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002266 case KeyEvent.ACTION_UP:
2267 return super.onKeyUp(keyCode, event);
2268
2269 case KeyEvent.ACTION_MULTIPLE:
2270 return super.onKeyMultiple(keyCode, count, event);
2271
2272 default: // shouldn't happen
2273 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002274 }
2275 }
2276
2277 /**
2278 * Scrolls up or down by the number of items currently present on screen.
2279 *
2280 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2281 * @return whether selection was moved
2282 */
2283 boolean pageScroll(int direction) {
2284 int nextPage = -1;
2285 boolean down = false;
2286
2287 if (direction == FOCUS_UP) {
2288 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2289 } else if (direction == FOCUS_DOWN) {
2290 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2291 down = true;
2292 }
2293
2294 if (nextPage >= 0) {
2295 int position = lookForSelectablePosition(nextPage, down);
2296 if (position >= 0) {
2297 mLayoutMode = LAYOUT_SPECIFIC;
2298 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2299
2300 if (down && position > mItemCount - getChildCount()) {
2301 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2302 }
2303
2304 if (!down && position < getChildCount()) {
2305 mLayoutMode = LAYOUT_FORCE_TOP;
2306 }
2307
2308 setSelectionInt(position);
2309 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002310 if (!awakenScrollBars()) {
2311 invalidate();
2312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002313
2314 return true;
2315 }
2316 }
2317
2318 return false;
2319 }
2320
2321 /**
2322 * Go to the last or first item if possible (not worrying about panning across or navigating
2323 * within the internal focus of the currently selected item.)
2324 *
2325 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2326 *
2327 * @return whether selection was moved
2328 */
2329 boolean fullScroll(int direction) {
2330 boolean moved = false;
2331 if (direction == FOCUS_UP) {
2332 if (mSelectedPosition != 0) {
2333 int position = lookForSelectablePosition(0, true);
2334 if (position >= 0) {
2335 mLayoutMode = LAYOUT_FORCE_TOP;
2336 setSelectionInt(position);
2337 invokeOnItemScrollListener();
2338 }
2339 moved = true;
2340 }
2341 } else if (direction == FOCUS_DOWN) {
2342 if (mSelectedPosition < mItemCount - 1) {
2343 int position = lookForSelectablePosition(mItemCount - 1, true);
2344 if (position >= 0) {
2345 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2346 setSelectionInt(position);
2347 invokeOnItemScrollListener();
2348 }
2349 moved = true;
2350 }
2351 }
2352
Mike Cleronf116bf82009-09-27 19:14:12 -07002353 if (moved && !awakenScrollBars()) {
2354 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002355 invalidate();
2356 }
2357
2358 return moved;
2359 }
2360
2361 /**
2362 * To avoid horizontal focus searches changing the selected item, we
2363 * manually focus search within the selected item (as applicable), and
2364 * prevent focus from jumping to something within another item.
2365 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2366 * @return Whether this consumes the key event.
2367 */
2368 private boolean handleHorizontalFocusWithinListItem(int direction) {
2369 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002370 throw new IllegalArgumentException("direction must be one of"
2371 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002372 }
2373
2374 final int numChildren = getChildCount();
2375 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2376 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002377 if (selectedView != null && selectedView.hasFocus() &&
2378 selectedView instanceof ViewGroup) {
2379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002380 final View currentFocus = selectedView.findFocus();
2381 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002382 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002383 if (nextFocus != null) {
2384 // do the math to get interesting rect in next focus' coordinates
2385 currentFocus.getFocusedRect(mTempRect);
2386 offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2387 offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2388 if (nextFocus.requestFocus(direction, mTempRect)) {
2389 return true;
2390 }
2391 }
2392 // we are blocking the key from being handled (by returning true)
2393 // if the global result is going to be some other view within this
2394 // list. this is to acheive the overall goal of having
2395 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002396 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2397 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002398 if (globalNextFocus != null) {
2399 return isViewAncestorOf(globalNextFocus, this);
2400 }
2401 }
2402 }
2403 return false;
2404 }
2405
2406 /**
2407 * Scrolls to the next or previous item if possible.
2408 *
2409 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2410 *
2411 * @return whether selection was moved
2412 */
2413 boolean arrowScroll(int direction) {
2414 try {
2415 mInLayout = true;
2416 final boolean handled = arrowScrollImpl(direction);
2417 if (handled) {
2418 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2419 }
2420 return handled;
2421 } finally {
2422 mInLayout = false;
2423 }
2424 }
2425
2426 /**
Adam Powell2a939112013-03-21 17:08:38 -07002427 * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
2428 * to move to. This can return a position currently not represented by a view on screen
2429 * but only in the direction given.
2430 *
2431 * @param selectedPos Current selected position to move from
2432 * @param direction Direction to move in
2433 * @return Desired selected position after moving in the given direction
2434 */
2435 private final int nextSelectedPositionForDirection(int selectedPos, int direction) {
2436 int nextSelected;
2437 if (direction == View.FOCUS_DOWN) {
2438 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2439 selectedPos + 1 :
2440 mFirstPosition;
2441 } else {
2442 final int lastPos = mFirstPosition + getChildCount() - 1;
Michael Wrightb482a0012013-04-08 14:37:01 -07002443 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
Adam Powell2a939112013-03-21 17:08:38 -07002444 selectedPos - 1 :
2445 lastPos;
2446 }
2447
2448 if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2449 return INVALID_POSITION;
2450 }
2451 return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2452 }
2453
2454 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002455 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2456 * whether there are focusable items etc.
2457 *
2458 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2459 * @return Whether any scrolling, selection or focus change occured.
2460 */
2461 private boolean arrowScrollImpl(int direction) {
2462 if (getChildCount() <= 0) {
2463 return false;
2464 }
2465
2466 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002467 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002468
Adam Powell2a939112013-03-21 17:08:38 -07002469 int nextSelectedPosition = nextSelectedPositionForDirection(selectedPos, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002470 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2471
2472 // if we are moving focus, we may OVERRIDE the default behavior
2473 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2474 if (focusResult != null) {
2475 nextSelectedPosition = focusResult.getSelectedPosition();
2476 amountToScroll = focusResult.getAmountToScroll();
2477 }
2478
2479 boolean needToRedraw = focusResult != null;
2480 if (nextSelectedPosition != INVALID_POSITION) {
2481 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2482 setSelectedPositionInt(nextSelectedPosition);
2483 setNextSelectedPositionInt(nextSelectedPosition);
2484 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002485 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002486 if (mItemsCanFocus && focusResult == null) {
2487 // there was no new view found to take focus, make sure we
2488 // don't leave focus with the old selection
2489 final View focused = getFocusedChild();
2490 if (focused != null) {
2491 focused.clearFocus();
2492 }
2493 }
2494 needToRedraw = true;
2495 checkSelectionChanged();
2496 }
2497
2498 if (amountToScroll > 0) {
2499 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2500 needToRedraw = true;
2501 }
2502
2503 // if we didn't find a new focusable, make sure any existing focused
2504 // item that was panned off screen gives up focus.
2505 if (mItemsCanFocus && (focusResult == null)
2506 && selectedView != null && selectedView.hasFocus()) {
2507 final View focused = selectedView.findFocus();
Mark Brophy1ea68892011-08-01 16:24:44 +01002508 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002509 focused.clearFocus();
2510 }
2511 }
2512
2513 // if the current selection is panned off, we need to remove the selection
2514 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2515 && !isViewAncestorOf(selectedView, this)) {
2516 selectedView = null;
2517 hideSelector();
2518
2519 // but we don't want to set the ressurect position (that would make subsequent
2520 // unhandled key events bring back the item we just scrolled off!)
2521 mResurrectToPosition = INVALID_POSITION;
2522 }
2523
2524 if (needToRedraw) {
2525 if (selectedView != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002526 positionSelector(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002527 mSelectedTop = selectedView.getTop();
2528 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002529 if (!awakenScrollBars()) {
2530 invalidate();
2531 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002532 invokeOnItemScrollListener();
2533 return true;
2534 }
2535
2536 return false;
2537 }
2538
2539 /**
2540 * When selection changes, it is possible that the previously selected or the
2541 * next selected item will change its size. If so, we need to offset some folks,
2542 * and re-layout the items as appropriate.
2543 *
2544 * @param selectedView The currently selected view (before changing selection).
2545 * should be <code>null</code> if there was no previous selection.
2546 * @param direction Either {@link android.view.View#FOCUS_UP} or
2547 * {@link android.view.View#FOCUS_DOWN}.
2548 * @param newSelectedPosition The position of the next selection.
2549 * @param newFocusAssigned whether new focus was assigned. This matters because
2550 * when something has focus, we don't want to show selection (ugh).
2551 */
2552 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2553 boolean newFocusAssigned) {
2554 if (newSelectedPosition == INVALID_POSITION) {
2555 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2556 }
2557
2558 // whether or not we are moving down or up, we want to preserve the
2559 // top of whatever view is on top:
2560 // - moving down: the view that had selection
2561 // - moving up: the view that is getting selection
2562 View topView;
2563 View bottomView;
2564 int topViewIndex, bottomViewIndex;
2565 boolean topSelected = false;
2566 final int selectedIndex = mSelectedPosition - mFirstPosition;
2567 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2568 if (direction == View.FOCUS_UP) {
2569 topViewIndex = nextSelectedIndex;
2570 bottomViewIndex = selectedIndex;
2571 topView = getChildAt(topViewIndex);
2572 bottomView = selectedView;
2573 topSelected = true;
2574 } else {
2575 topViewIndex = selectedIndex;
2576 bottomViewIndex = nextSelectedIndex;
2577 topView = selectedView;
2578 bottomView = getChildAt(bottomViewIndex);
2579 }
2580
2581 final int numChildren = getChildCount();
2582
2583 // start with top view: is it changing size?
2584 if (topView != null) {
2585 topView.setSelected(!newFocusAssigned && topSelected);
2586 measureAndAdjustDown(topView, topViewIndex, numChildren);
2587 }
2588
2589 // is the bottom view changing size?
2590 if (bottomView != null) {
2591 bottomView.setSelected(!newFocusAssigned && !topSelected);
2592 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2593 }
2594 }
2595
2596 /**
2597 * Re-measure a child, and if its height changes, lay it out preserving its
2598 * top, and adjust the children below it appropriately.
2599 * @param child The child
2600 * @param childIndex The view group index of the child.
2601 * @param numChildren The number of children in the view group.
2602 */
2603 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2604 int oldHeight = child.getHeight();
2605 measureItem(child);
2606 if (child.getMeasuredHeight() != oldHeight) {
2607 // lay out the view, preserving its top
2608 relayoutMeasuredItem(child);
2609
2610 // adjust views below appropriately
2611 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2612 for (int i = childIndex + 1; i < numChildren; i++) {
2613 getChildAt(i).offsetTopAndBottom(heightDelta);
2614 }
2615 }
2616 }
2617
2618 /**
2619 * Measure a particular list child.
2620 * TODO: unify with setUpChild.
2621 * @param child The child.
2622 */
2623 private void measureItem(View child) {
2624 ViewGroup.LayoutParams p = child.getLayoutParams();
2625 if (p == null) {
2626 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002627 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002628 ViewGroup.LayoutParams.WRAP_CONTENT);
2629 }
2630
2631 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2632 mListPadding.left + mListPadding.right, p.width);
2633 int lpHeight = p.height;
2634 int childHeightSpec;
2635 if (lpHeight > 0) {
2636 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2637 } else {
2638 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2639 }
2640 child.measure(childWidthSpec, childHeightSpec);
2641 }
2642
2643 /**
2644 * Layout a child that has been measured, preserving its top position.
2645 * TODO: unify with setUpChild.
2646 * @param child The child.
2647 */
2648 private void relayoutMeasuredItem(View child) {
2649 final int w = child.getMeasuredWidth();
2650 final int h = child.getMeasuredHeight();
2651 final int childLeft = mListPadding.left;
2652 final int childRight = childLeft + w;
2653 final int childTop = child.getTop();
2654 final int childBottom = childTop + h;
2655 child.layout(childLeft, childTop, childRight, childBottom);
2656 }
2657
2658 /**
2659 * @return The amount to preview next items when arrow srolling.
2660 */
2661 private int getArrowScrollPreviewLength() {
2662 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2663 }
2664
2665 /**
2666 * Determine how much we need to scroll in order to get the next selected view
2667 * visible, with a fading edge showing below as applicable. The amount is
2668 * capped at {@link #getMaxScrollAmount()} .
2669 *
2670 * @param direction either {@link android.view.View#FOCUS_UP} or
2671 * {@link android.view.View#FOCUS_DOWN}.
2672 * @param nextSelectedPosition The position of the next selection, or
2673 * {@link #INVALID_POSITION} if there is no next selectable position
2674 * @return The amount to scroll. Note: this is always positive! Direction
2675 * needs to be taken into account when actually scrolling.
2676 */
2677 private int amountToScroll(int direction, int nextSelectedPosition) {
2678 final int listBottom = getHeight() - mListPadding.bottom;
2679 final int listTop = mListPadding.top;
2680
Justin Ho1ad11b92013-02-25 16:09:20 -08002681 int numChildren = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002682
2683 if (direction == View.FOCUS_DOWN) {
2684 int indexToMakeVisible = numChildren - 1;
2685 if (nextSelectedPosition != INVALID_POSITION) {
2686 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2687 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002688 while (numChildren <= indexToMakeVisible) {
2689 // Child to view is not attached yet.
2690 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2691 numChildren++;
2692 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002693 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2694 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2695
2696 int goalBottom = listBottom;
2697 if (positionToMakeVisible < mItemCount - 1) {
2698 goalBottom -= getArrowScrollPreviewLength();
2699 }
2700
2701 if (viewToMakeVisible.getBottom() <= goalBottom) {
2702 // item is fully visible.
2703 return 0;
2704 }
2705
2706 if (nextSelectedPosition != INVALID_POSITION
2707 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2708 // item already has enough of it visible, changing selection is good enough
2709 return 0;
2710 }
2711
2712 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2713
2714 if ((mFirstPosition + numChildren) == mItemCount) {
2715 // last is last in list -> make sure we don't scroll past it
2716 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2717 amountToScroll = Math.min(amountToScroll, max);
2718 }
2719
2720 return Math.min(amountToScroll, getMaxScrollAmount());
2721 } else {
2722 int indexToMakeVisible = 0;
2723 if (nextSelectedPosition != INVALID_POSITION) {
2724 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2725 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002726 while (indexToMakeVisible < 0) {
2727 // Child to view is not attached yet.
2728 addViewAbove(getChildAt(0), mFirstPosition);
2729 mFirstPosition--;
2730 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002732 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2733 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2734 int goalTop = listTop;
2735 if (positionToMakeVisible > 0) {
2736 goalTop += getArrowScrollPreviewLength();
2737 }
2738 if (viewToMakeVisible.getTop() >= goalTop) {
2739 // item is fully visible.
2740 return 0;
2741 }
2742
2743 if (nextSelectedPosition != INVALID_POSITION &&
2744 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2745 // item already has enough of it visible, changing selection is good enough
2746 return 0;
2747 }
2748
2749 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2750 if (mFirstPosition == 0) {
2751 // first is first in list -> make sure we don't scroll past it
2752 final int max = listTop - getChildAt(0).getTop();
2753 amountToScroll = Math.min(amountToScroll, max);
2754 }
2755 return Math.min(amountToScroll, getMaxScrollAmount());
2756 }
2757 }
2758
2759 /**
2760 * Holds results of focus aware arrow scrolling.
2761 */
2762 static private class ArrowScrollFocusResult {
2763 private int mSelectedPosition;
2764 private int mAmountToScroll;
2765
2766 /**
2767 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2768 */
2769 void populate(int selectedPosition, int amountToScroll) {
2770 mSelectedPosition = selectedPosition;
2771 mAmountToScroll = amountToScroll;
2772 }
2773
2774 public int getSelectedPosition() {
2775 return mSelectedPosition;
2776 }
2777
2778 public int getAmountToScroll() {
2779 return mAmountToScroll;
2780 }
2781 }
2782
2783 /**
2784 * @param direction either {@link android.view.View#FOCUS_UP} or
2785 * {@link android.view.View#FOCUS_DOWN}.
2786 * @return The position of the next selectable position of the views that
2787 * are currently visible, taking into account the fact that there might
2788 * be no selection. Returns {@link #INVALID_POSITION} if there is no
2789 * selectable view on screen in the given direction.
2790 */
2791 private int lookForSelectablePositionOnScreen(int direction) {
2792 final int firstPosition = mFirstPosition;
2793 if (direction == View.FOCUS_DOWN) {
2794 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2795 mSelectedPosition + 1 :
2796 firstPosition;
2797 if (startPos >= mAdapter.getCount()) {
2798 return INVALID_POSITION;
2799 }
2800 if (startPos < firstPosition) {
2801 startPos = firstPosition;
2802 }
2803
2804 final int lastVisiblePos = getLastVisiblePosition();
2805 final ListAdapter adapter = getAdapter();
2806 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2807 if (adapter.isEnabled(pos)
2808 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2809 return pos;
2810 }
2811 }
2812 } else {
2813 int last = firstPosition + getChildCount() - 1;
2814 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2815 mSelectedPosition - 1 :
2816 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08002817 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002818 return INVALID_POSITION;
2819 }
2820 if (startPos > last) {
2821 startPos = last;
2822 }
2823
2824 final ListAdapter adapter = getAdapter();
2825 for (int pos = startPos; pos >= firstPosition; pos--) {
2826 if (adapter.isEnabled(pos)
2827 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2828 return pos;
2829 }
2830 }
2831 }
2832 return INVALID_POSITION;
2833 }
2834
2835 /**
2836 * Do an arrow scroll based on focus searching. If a new view is
2837 * given focus, return the selection delta and amount to scroll via
2838 * an {@link ArrowScrollFocusResult}, otherwise, return null.
2839 *
2840 * @param direction either {@link android.view.View#FOCUS_UP} or
2841 * {@link android.view.View#FOCUS_DOWN}.
2842 * @return The result if focus has changed, or <code>null</code>.
2843 */
2844 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2845 final View selectedView = getSelectedView();
2846 View newFocus;
2847 if (selectedView != null && selectedView.hasFocus()) {
2848 View oldFocus = selectedView.findFocus();
2849 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2850 } else {
2851 if (direction == View.FOCUS_DOWN) {
2852 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2853 final int listTop = mListPadding.top +
2854 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2855 final int ySearchPoint =
2856 (selectedView != null && selectedView.getTop() > listTop) ?
2857 selectedView.getTop() :
2858 listTop;
2859 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2860 } else {
2861 final boolean bottomFadingEdgeShowing =
2862 (mFirstPosition + getChildCount() - 1) < mItemCount;
2863 final int listBottom = getHeight() - mListPadding.bottom -
2864 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2865 final int ySearchPoint =
2866 (selectedView != null && selectedView.getBottom() < listBottom) ?
2867 selectedView.getBottom() :
2868 listBottom;
2869 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2870 }
2871 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2872 }
2873
2874 if (newFocus != null) {
2875 final int positionOfNewFocus = positionOfNewFocus(newFocus);
2876
2877 // if the focus change is in a different new position, make sure
2878 // we aren't jumping over another selectable position
2879 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2880 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2881 if (selectablePosition != INVALID_POSITION &&
2882 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2883 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2884 return null;
2885 }
2886 }
2887
2888 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2889
2890 final int maxScrollAmount = getMaxScrollAmount();
2891 if (focusScroll < maxScrollAmount) {
2892 // not moving too far, safe to give next view focus
2893 newFocus.requestFocus(direction);
2894 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2895 return mArrowScrollFocusResult;
2896 } else if (distanceToView(newFocus) < maxScrollAmount){
2897 // Case to consider:
2898 // too far to get entire next focusable on screen, but by going
2899 // max scroll amount, we are getting it at least partially in view,
2900 // so give it focus and scroll the max ammount.
2901 newFocus.requestFocus(direction);
2902 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2903 return mArrowScrollFocusResult;
2904 }
2905 }
2906 return null;
2907 }
2908
2909 /**
2910 * @param newFocus The view that would have focus.
2911 * @return the position that contains newFocus
2912 */
2913 private int positionOfNewFocus(View newFocus) {
2914 final int numChildren = getChildCount();
2915 for (int i = 0; i < numChildren; i++) {
2916 final View child = getChildAt(i);
2917 if (isViewAncestorOf(newFocus, child)) {
2918 return mFirstPosition + i;
2919 }
2920 }
2921 throw new IllegalArgumentException("newFocus is not a child of any of the"
2922 + " children of the list!");
2923 }
2924
2925 /**
2926 * Return true if child is an ancestor of parent, (or equal to the parent).
2927 */
2928 private boolean isViewAncestorOf(View child, View parent) {
2929 if (child == parent) {
2930 return true;
2931 }
2932
2933 final ViewParent theParent = child.getParent();
2934 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2935 }
2936
2937 /**
2938 * Determine how much we need to scroll in order to get newFocus in view.
2939 * @param direction either {@link android.view.View#FOCUS_UP} or
2940 * {@link android.view.View#FOCUS_DOWN}.
2941 * @param newFocus The view that would take focus.
2942 * @param positionOfNewFocus The position of the list item containing newFocus
2943 * @return The amount to scroll. Note: this is always positive! Direction
2944 * needs to be taken into account when actually scrolling.
2945 */
2946 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2947 int amountToScroll = 0;
2948 newFocus.getDrawingRect(mTempRect);
2949 offsetDescendantRectToMyCoords(newFocus, mTempRect);
2950 if (direction == View.FOCUS_UP) {
2951 if (mTempRect.top < mListPadding.top) {
2952 amountToScroll = mListPadding.top - mTempRect.top;
2953 if (positionOfNewFocus > 0) {
2954 amountToScroll += getArrowScrollPreviewLength();
2955 }
2956 }
2957 } else {
2958 final int listBottom = getHeight() - mListPadding.bottom;
2959 if (mTempRect.bottom > listBottom) {
2960 amountToScroll = mTempRect.bottom - listBottom;
2961 if (positionOfNewFocus < mItemCount - 1) {
2962 amountToScroll += getArrowScrollPreviewLength();
2963 }
2964 }
2965 }
2966 return amountToScroll;
2967 }
2968
2969 /**
2970 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08002971 * direction.
2972 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002973 * @param descendant A descendant of this list.
2974 * @return The distance, or 0 if the nearest edge is already on screen.
2975 */
2976 private int distanceToView(View descendant) {
2977 int distance = 0;
2978 descendant.getDrawingRect(mTempRect);
2979 offsetDescendantRectToMyCoords(descendant, mTempRect);
2980 final int listBottom = mBottom - mTop - mListPadding.bottom;
2981 if (mTempRect.bottom < mListPadding.top) {
2982 distance = mListPadding.top - mTempRect.bottom;
2983 } else if (mTempRect.top > listBottom) {
2984 distance = mTempRect.top - listBottom;
2985 }
2986 return distance;
2987 }
2988
2989
2990 /**
2991 * Scroll the children by amount, adding a view at the end and removing
2992 * views that fall off as necessary.
2993 *
2994 * @param amount The amount (positive or negative) to scroll.
2995 */
2996 private void scrollListItemsBy(int amount) {
2997 offsetChildrenTopAndBottom(amount);
2998
2999 final int listBottom = getHeight() - mListPadding.bottom;
3000 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003001 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003002
3003 if (amount < 0) {
3004 // shifted items up
3005
3006 // may need to pan views into the bottom space
3007 int numChildren = getChildCount();
3008 View last = getChildAt(numChildren - 1);
3009 while (last.getBottom() < listBottom) {
3010 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3011 if (lastVisiblePosition < mItemCount - 1) {
3012 last = addViewBelow(last, lastVisiblePosition);
3013 numChildren++;
3014 } else {
3015 break;
3016 }
3017 }
3018
3019 // may have brought in the last child of the list that is skinnier
3020 // than the fading edge, thereby leaving space at the end. need
3021 // to shift back
3022 if (last.getBottom() < listBottom) {
3023 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3024 }
3025
3026 // top views may be panned off screen
3027 View first = getChildAt(0);
3028 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003029 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3030 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003031 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003032 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003033 detachViewFromParent(first);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003034 first = getChildAt(0);
3035 mFirstPosition++;
3036 }
3037 } else {
3038 // shifted items down
3039 View first = getChildAt(0);
3040
3041 // may need to pan views into top
3042 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3043 first = addViewAbove(first, mFirstPosition);
3044 mFirstPosition--;
3045 }
3046
3047 // may have brought the very first child of the list in too far and
3048 // need to shift it back
3049 if (first.getTop() > listTop) {
3050 offsetChildrenTopAndBottom(listTop - first.getTop());
3051 }
3052
3053 int lastIndex = getChildCount() - 1;
3054 View last = getChildAt(lastIndex);
3055
3056 // bottom view may be panned off screen
3057 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003058 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3059 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003060 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003061 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003062 detachViewFromParent(last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003063 last = getChildAt(--lastIndex);
3064 }
3065 }
3066 }
3067
3068 private View addViewAbove(View theView, int position) {
3069 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08003070 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003071 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003072 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3073 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003074 return view;
3075 }
3076
3077 private View addViewBelow(View theView, int position) {
3078 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08003079 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003080 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003081 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3082 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003083 return view;
3084 }
3085
3086 /**
3087 * Indicates that the views created by the ListAdapter can contain focusable
3088 * items.
3089 *
3090 * @param itemsCanFocus true if items can get focus, false otherwise
3091 */
3092 public void setItemsCanFocus(boolean itemsCanFocus) {
3093 mItemsCanFocus = itemsCanFocus;
3094 if (!itemsCanFocus) {
3095 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3096 }
3097 }
3098
3099 /**
3100 * @return Whether the views created by the ListAdapter can contain focusable
3101 * items.
3102 */
3103 public boolean getItemsCanFocus() {
3104 return mItemsCanFocus;
3105 }
3106
3107 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003108 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003109 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f52009-05-15 16:03:59 -07003110 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003111 if (retValue) {
3112 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003113 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003114 View first = getChildAt(0);
3115 if (first == null || first.getTop() > listTop) {
3116 return false;
3117 }
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003118 final int listBottom = getHeight() -
3119 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003120 View last = getChildAt(getChildCount() - 1);
3121 if (last == null || last.getBottom() < listBottom) {
3122 return false;
3123 }
3124 }
3125 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003126 }
3127
3128 @Override
3129 public void setCacheColorHint(int color) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003130 final boolean opaque = (color >>> 24) == 0xFF;
3131 mIsCacheColorOpaque = opaque;
3132 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003133 if (mDividerPaint == null) {
3134 mDividerPaint = new Paint();
3135 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003136 mDividerPaint.setColor(color);
3137 }
Romain Guy24443ea2009-05-11 11:56:30 -07003138 super.setCacheColorHint(color);
3139 }
Adam Powell637d3372010-08-25 14:37:03 -07003140
3141 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3142 final int height = drawable.getMinimumHeight();
3143
3144 canvas.save();
3145 canvas.clipRect(bounds);
3146
3147 final int span = bounds.bottom - bounds.top;
3148 if (span < height) {
3149 bounds.top = bounds.bottom - height;
3150 }
3151
3152 drawable.setBounds(bounds);
3153 drawable.draw(canvas);
3154
3155 canvas.restore();
3156 }
3157
3158 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3159 final int height = drawable.getMinimumHeight();
3160
3161 canvas.save();
3162 canvas.clipRect(bounds);
3163
3164 final int span = bounds.bottom - bounds.top;
3165 if (span < height) {
3166 bounds.bottom = bounds.top + height;
3167 }
3168
3169 drawable.setBounds(bounds);
3170 drawable.draw(canvas);
3171
3172 canvas.restore();
3173 }
3174
Romain Guy24443ea2009-05-11 11:56:30 -07003175 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003176 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003177 if (mCachingStarted) {
3178 mCachingActive = true;
3179 }
3180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003181 // Draw the dividers
3182 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003183 final Drawable overscrollHeader = mOverScrollHeader;
3184 final Drawable overscrollFooter = mOverScrollFooter;
3185 final boolean drawOverscrollHeader = overscrollHeader != null;
3186 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003187 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003188
Adam Powell637d3372010-08-25 14:37:03 -07003189 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003190 // Only modify the top and bottom in the loop, we set the left and right here
3191 final Rect bounds = mTempRect;
3192 bounds.left = mPaddingLeft;
3193 bounds.right = mRight - mLeft - mPaddingRight;
3194
3195 final int count = getChildCount();
3196 final int headerCount = mHeaderViewInfos.size();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003197 final int itemCount = mItemCount;
3198 final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003199 final boolean headerDividers = mHeaderDividersEnabled;
3200 final boolean footerDividers = mFooterDividersEnabled;
3201 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003202 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3203 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003204 // If the list is opaque *and* the background is not, we want to
3205 // fill a rect where the dividers would be for non-selectable items
3206 // If the list is opaque and the background is also opaque, we don't
3207 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003208 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003209
3210 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003211 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003212 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003213 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003214 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003215
Adam Powell94566552011-01-05 23:25:33 -08003216 int effectivePaddingTop = 0;
3217 int effectivePaddingBottom = 0;
3218 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3219 effectivePaddingTop = mListPadding.top;
3220 effectivePaddingBottom = mListPadding.bottom;
3221 }
3222
3223 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003224 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003225 int bottom = 0;
Adam Powell0b8bb422010-02-08 14:30:45 -08003226
Adam Powell637d3372010-08-25 14:37:03 -07003227 // Draw top divider or header for overscroll
3228 final int scrollY = mScrollY;
3229 if (count > 0 && scrollY < 0) {
3230 if (drawOverscrollHeader) {
3231 bounds.bottom = 0;
3232 bounds.top = scrollY;
3233 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3234 } else if (drawDividers) {
3235 bounds.bottom = 0;
3236 bounds.top = -dividerHeight;
3237 drawDivider(canvas, bounds, -1);
3238 }
3239 }
3240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003241 for (int i = 0; i < count; i++) {
3242 if ((headerDividers || first + i >= headerCount) &&
3243 (footerDividers || first + i < footerLimit)) {
3244 View child = getChildAt(i);
3245 bottom = child.getBottom();
Romain Guy2bed2272009-03-24 18:23:21 -07003246 // Don't draw dividers next to items that are not enabled
Adam Powell637d3372010-08-25 14:37:03 -07003247
3248 if (drawDividers &&
3249 (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
3250 if ((areAllItemsSelectable ||
3251 (adapter.isEnabled(first + i) && (i == count - 1 ||
3252 adapter.isEnabled(first + i + 1))))) {
3253 bounds.top = bottom;
3254 bounds.bottom = bottom + dividerHeight;
3255 drawDivider(canvas, bounds, i);
3256 } else if (fillForMissingDividers) {
3257 bounds.top = bottom;
3258 bounds.bottom = bottom + dividerHeight;
3259 canvas.drawRect(bounds, paint);
3260 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003261 }
3262 }
3263 }
Adam Powell637d3372010-08-25 14:37:03 -07003264
3265 final int overFooterBottom = mBottom + mScrollY;
3266 if (drawOverscrollFooter && first + count == itemCount &&
3267 overFooterBottom > bottom) {
3268 bounds.top = bottom;
3269 bounds.bottom = overFooterBottom;
3270 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3271 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003272 } else {
3273 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003274
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003275 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003276
Adam Powell637d3372010-08-25 14:37:03 -07003277 if (count > 0 && drawOverscrollHeader) {
3278 bounds.top = scrollY;
3279 bounds.bottom = getChildAt(0).getTop();
3280 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3281 }
3282
3283 final int start = drawOverscrollHeader ? 1 : 0;
3284 for (int i = start; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003285 if ((headerDividers || first + i >= headerCount) &&
3286 (footerDividers || first + i < footerLimit)) {
3287 View child = getChildAt(i);
3288 top = child.getTop();
Romain Guy2bed2272009-03-24 18:23:21 -07003289 // Don't draw dividers next to items that are not enabled
Romain Guy0211a0a2011-02-14 16:34:59 -08003290 if (top > effectivePaddingTop) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003291 if ((areAllItemsSelectable ||
3292 (adapter.isEnabled(first + i) && (i == count - 1 ||
3293 adapter.isEnabled(first + i + 1))))) {
3294 bounds.top = top - dividerHeight;
3295 bounds.bottom = top;
3296 // Give the method the child ABOVE the divider, so we
3297 // subtract one from our child
3298 // position. Give -1 when there is no child above the
3299 // divider.
3300 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003301 } else if (fillForMissingDividers) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003302 bounds.top = top - dividerHeight;
3303 bounds.bottom = top;
3304 canvas.drawRect(bounds, paint);
3305 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003306 }
3307 }
3308 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003309
Romain Guy179de8a2010-07-09 13:27:00 -07003310 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003311 if (drawOverscrollFooter) {
3312 final int absListBottom = mBottom;
3313 bounds.top = absListBottom;
3314 bounds.bottom = absListBottom + scrollY;
3315 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3316 } else if (drawDividers) {
3317 bounds.top = listBottom;
3318 bounds.bottom = listBottom + dividerHeight;
3319 drawDivider(canvas, bounds, -1);
3320 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003321 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003322 }
3323 }
3324
3325 // Draw the indicators (these should be drawn above the dividers) and children
3326 super.dispatchDraw(canvas);
3327 }
3328
Romain Guy0211a0a2011-02-14 16:34:59 -08003329 @Override
3330 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3331 boolean more = super.drawChild(canvas, child, drawingTime);
3332 if (mCachingActive && child.mCachingFailed) {
3333 mCachingActive = false;
3334 }
3335 return more;
3336 }
3337
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003338 /**
3339 * Draws a divider for the given child in the given bounds.
3340 *
3341 * @param canvas The canvas to draw to.
3342 * @param bounds The bounds of the divider.
3343 * @param childIndex The index of child (of the View) above the divider.
3344 * This will be -1 if there is no child above the divider to be
3345 * drawn.
3346 */
3347 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3348 // This widget draws the same divider for all children
3349 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003350
Romain Guy95930e12010-10-04 13:46:02 -07003351 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003352 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 }
3354
3355 /**
3356 * Returns the drawable that will be drawn between each item in the list.
3357 *
3358 * @return the current drawable drawn between list elements
3359 */
3360 public Drawable getDivider() {
3361 return mDivider;
3362 }
3363
3364 /**
3365 * Sets the drawable that will be drawn between each item in the list. If the drawable does
3366 * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3367 *
3368 * @param divider The drawable to use.
3369 */
3370 public void setDivider(Drawable divider) {
3371 if (divider != null) {
3372 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003373 } else {
3374 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003375 }
3376 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003377 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003378 requestLayout();
3379 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003380 }
3381
3382 /**
3383 * @return Returns the height of the divider that will be drawn between each item in the list.
3384 */
3385 public int getDividerHeight() {
3386 return mDividerHeight;
3387 }
3388
3389 /**
3390 * Sets the height of the divider that will be drawn between each item in the list. Calling
3391 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3392 *
3393 * @param height The new height of the divider in pixels.
3394 */
3395 public void setDividerHeight(int height) {
3396 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003397 requestLayout();
3398 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003399 }
3400
3401 /**
3402 * Enables or disables the drawing of the divider for header views.
3403 *
3404 * @param headerDividersEnabled True to draw the headers, false otherwise.
3405 *
3406 * @see #setFooterDividersEnabled(boolean)
3407 * @see #addHeaderView(android.view.View)
3408 */
3409 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3410 mHeaderDividersEnabled = headerDividersEnabled;
3411 invalidate();
3412 }
3413
3414 /**
3415 * Enables or disables the drawing of the divider for footer views.
3416 *
3417 * @param footerDividersEnabled True to draw the footers, false otherwise.
3418 *
3419 * @see #setHeaderDividersEnabled(boolean)
3420 * @see #addFooterView(android.view.View)
3421 */
3422 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3423 mFooterDividersEnabled = footerDividersEnabled;
3424 invalidate();
3425 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003426
Adam Powell637d3372010-08-25 14:37:03 -07003427 /**
3428 * Sets the drawable that will be drawn above all other list content.
3429 * This area can become visible when the user overscrolls the list.
3430 *
3431 * @param header The drawable to use
3432 */
3433 public void setOverscrollHeader(Drawable header) {
3434 mOverScrollHeader = header;
3435 if (mScrollY < 0) {
3436 invalidate();
3437 }
3438 }
3439
3440 /**
3441 * @return The drawable that will be drawn above all other list content
3442 */
3443 public Drawable getOverscrollHeader() {
3444 return mOverScrollHeader;
3445 }
3446
3447 /**
3448 * Sets the drawable that will be drawn below all other list content.
3449 * This area can become visible when the user overscrolls the list,
3450 * or when the list's content does not fully fill the container area.
3451 *
3452 * @param footer The drawable to use
3453 */
3454 public void setOverscrollFooter(Drawable footer) {
3455 mOverScrollFooter = footer;
3456 invalidate();
3457 }
3458
3459 /**
3460 * @return The drawable that will be drawn below all other list content
3461 */
3462 public Drawable getOverscrollFooter() {
3463 return mOverScrollFooter;
3464 }
3465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003466 @Override
3467 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3468 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3469
Adam Powelle1bf4862011-09-02 16:56:20 -07003470 final ListAdapter adapter = mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003471 int closetChildIndex = -1;
Adam Powelldcce1212011-10-31 16:41:21 -07003472 int closestChildTop = 0;
Adam Powelle1bf4862011-09-02 16:56:20 -07003473 if (adapter != null && gainFocus && previouslyFocusedRect != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003474 previouslyFocusedRect.offset(mScrollX, mScrollY);
3475
Adam Powelld7507832010-02-18 15:40:33 -08003476 // Don't cache the result of getChildCount or mFirstPosition here,
3477 // it could change in layoutChildren.
3478 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003479 mLayoutMode = LAYOUT_NORMAL;
3480 layoutChildren();
3481 }
3482
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003483 // figure out which item should be selected based on previously
3484 // focused rect
3485 Rect otherRect = mTempRect;
3486 int minDistance = Integer.MAX_VALUE;
3487 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003488 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003489
3490 for (int i = 0; i < childCount; i++) {
3491 // only consider selectable views
3492 if (!adapter.isEnabled(firstPosition + i)) {
3493 continue;
3494 }
3495
3496 View other = getChildAt(i);
3497 other.getDrawingRect(otherRect);
3498 offsetDescendantRectToMyCoords(other, otherRect);
3499 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3500
3501 if (distance < minDistance) {
3502 minDistance = distance;
3503 closetChildIndex = i;
Adam Powelldcce1212011-10-31 16:41:21 -07003504 closestChildTop = other.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003505 }
3506 }
3507 }
3508
3509 if (closetChildIndex >= 0) {
Adam Powelldcce1212011-10-31 16:41:21 -07003510 setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003511 } else {
3512 requestLayout();
3513 }
3514 }
3515
3516
3517 /*
3518 * (non-Javadoc)
3519 *
3520 * Children specified in XML are assumed to be header views. After we have
3521 * parsed them move them out of the children list and into mHeaderViews.
3522 */
3523 @Override
3524 protected void onFinishInflate() {
3525 super.onFinishInflate();
3526
3527 int count = getChildCount();
3528 if (count > 0) {
3529 for (int i = 0; i < count; ++i) {
3530 addHeaderView(getChildAt(i));
3531 }
3532 removeAllViews();
3533 }
3534 }
3535
3536 /* (non-Javadoc)
3537 * @see android.view.View#findViewById(int)
3538 * First look in our children, then in any header and footer views that may be scrolled off.
3539 */
3540 @Override
3541 protected View findViewTraversal(int id) {
3542 View v;
3543 v = super.findViewTraversal(id);
3544 if (v == null) {
3545 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3546 if (v != null) {
3547 return v;
3548 }
3549 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3550 if (v != null) {
3551 return v;
3552 }
3553 }
3554 return v;
3555 }
3556
3557 /* (non-Javadoc)
3558 *
3559 * Look in the passed in list of headers or footers for the view.
3560 */
3561 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3562 if (where != null) {
3563 int len = where.size();
3564 View v;
3565
3566 for (int i = 0; i < len; i++) {
3567 v = where.get(i).view;
3568
3569 if (!v.isRootNamespace()) {
3570 v = v.findViewById(id);
3571
3572 if (v != null) {
3573 return v;
3574 }
3575 }
3576 }
3577 }
3578 return null;
3579 }
3580
3581 /* (non-Javadoc)
Jeff Brown4e6319b2010-12-13 10:36:51 -08003582 * @see android.view.View#findViewWithTag(Object)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003583 * First look in our children, then in any header and footer views that may be scrolled off.
3584 */
3585 @Override
3586 protected View findViewWithTagTraversal(Object tag) {
3587 View v;
3588 v = super.findViewWithTagTraversal(tag);
3589 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003590 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003591 if (v != null) {
3592 return v;
3593 }
3594
Jeff Brown4e6319b2010-12-13 10:36:51 -08003595 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003596 if (v != null) {
3597 return v;
3598 }
3599 }
3600 return v;
3601 }
3602
3603 /* (non-Javadoc)
3604 *
3605 * Look in the passed in list of headers or footers for the view with the tag.
3606 */
Jeff Brown4e6319b2010-12-13 10:36:51 -08003607 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003608 if (where != null) {
3609 int len = where.size();
3610 View v;
3611
3612 for (int i = 0; i < len; i++) {
3613 v = where.get(i).view;
3614
3615 if (!v.isRootNamespace()) {
3616 v = v.findViewWithTag(tag);
3617
3618 if (v != null) {
3619 return v;
3620 }
3621 }
3622 }
3623 }
3624 return null;
3625 }
3626
Jeff Brown4e6319b2010-12-13 10:36:51 -08003627 /**
3628 * @hide
3629 * @see android.view.View#findViewByPredicate(Predicate)
3630 * First look in our children, then in any header and footer views that may be scrolled off.
3631 */
3632 @Override
Jeff Brown4dfbec22011-08-15 14:55:37 -07003633 protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003634 View v;
Jeff Brown4dfbec22011-08-15 14:55:37 -07003635 v = super.findViewByPredicateTraversal(predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003636 if (v == null) {
Jeff Brown4dfbec22011-08-15 14:55:37 -07003637 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003638 if (v != null) {
3639 return v;
3640 }
3641
Jeff Brown4dfbec22011-08-15 14:55:37 -07003642 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003643 if (v != null) {
3644 return v;
3645 }
3646 }
3647 return v;
3648 }
3649
3650 /* (non-Javadoc)
3651 *
3652 * Look in the passed in list of headers or footers for the first view that matches
3653 * the predicate.
3654 */
3655 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
Jeff Brown4dfbec22011-08-15 14:55:37 -07003656 Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003657 if (where != null) {
3658 int len = where.size();
3659 View v;
3660
3661 for (int i = 0; i < len; i++) {
3662 v = where.get(i).view;
3663
Jeff Brown4dfbec22011-08-15 14:55:37 -07003664 if (v != childToSkip && !v.isRootNamespace()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003665 v = v.findViewByPredicate(predicate);
3666
3667 if (v != null) {
3668 return v;
3669 }
3670 }
3671 }
3672 }
3673 return null;
3674 }
3675
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003676 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003677 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003678 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3679 *
3680 * @return A new array which contains the id of each checked item in the
3681 * list.
3682 *
Adam Powell463ceff2010-03-09 11:50:51 -08003683 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003684 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003685 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003686 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003687 // Use new behavior that correctly handles stable ID mapping.
3688 if (mAdapter != null && mAdapter.hasStableIds()) {
3689 return getCheckedItemIds();
3690 }
3691
3692 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3693 // Fall back to it to support legacy apps.
3694 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3695 final SparseBooleanArray states = mCheckStates;
3696 final int count = states.size();
3697 final long[] ids = new long[count];
3698 final ListAdapter adapter = mAdapter;
3699
3700 int checkedCount = 0;
3701 for (int i = 0; i < count; i++) {
3702 if (states.valueAt(i)) {
3703 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3704 }
3705 }
3706
3707 // Trim array if needed. mCheckStates may contain false values
3708 // resulting in checkedCount being smaller than count.
3709 if (checkedCount == count) {
3710 return ids;
3711 } else {
3712 final long[] result = new long[checkedCount];
3713 System.arraycopy(ids, 0, result, 0, checkedCount);
3714
3715 return result;
3716 }
3717 }
3718 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08003719 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003720
3721 @Override
3722 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
3723 super.onInitializeAccessibilityEvent(event);
3724 event.setClassName(ListView.class.getName());
3725 }
3726
3727 @Override
3728 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
3729 super.onInitializeAccessibilityNodeInfo(info);
3730 info.setClassName(ListView.class.getName());
3731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003732}