blob: af954c9022e640ac55965dcd216ce264ccb7eb45 [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;
32import android.util.SparseBooleanArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.view.FocusFinder;
34import android.view.KeyEvent;
35import android.view.MotionEvent;
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;
svetoslavganov75986cf2009-05-14 22:28:01 -070041import android.view.accessibility.AccessibilityEvent;
Winson Chung499cb9f2010-07-16 11:18:17 -070042import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import java.util.ArrayList;
45
46/*
47 * Implementation Notes:
48 *
49 * Some terminology:
50 *
51 * index - index of the items that are currently visible
52 * position - index of the items in the cursor
53 */
54
55
56/**
57 * A view that shows items in a vertically scrolling list. The items
58 * come from the {@link ListAdapter} associated with this view.
59 *
Scott Main41ec6532010-08-19 16:57:07 -070060 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-listview.html">List View
61 * tutorial</a>.</p>
62 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 * @attr ref android.R.styleable#ListView_entries
64 * @attr ref android.R.styleable#ListView_divider
65 * @attr ref android.R.styleable#ListView_dividerHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 * @attr ref android.R.styleable#ListView_headerDividersEnabled
67 * @attr ref android.R.styleable#ListView_footerDividersEnabled
68 */
Winson Chung499cb9f2010-07-16 11:18:17 -070069@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070public class ListView extends AbsListView {
71 /**
72 * Used to indicate a no preference for a position type.
73 */
74 static final int NO_POSITION = -1;
75
76 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 * When arrow scrolling, ListView will never scroll more than this factor
78 * times the height of the list.
79 */
80 private static final float MAX_SCROLL_FACTOR = 0.33f;
81
82 /**
83 * When arrow scrolling, need a certain amount of pixels to preview next
84 * items. This is usually the fading edge, but if that is small enough,
85 * we want to make sure we preview at least this many pixels.
86 */
87 private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
88
89 /**
90 * A class that represents a fixed view in a list, for example a header at the top
91 * or a footer at the bottom.
92 */
93 public class FixedViewInfo {
94 /** The view to add to the list */
95 public View view;
96 /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
97 public Object data;
98 /** <code>true</code> if the fixed view should be selectable in the list */
99 public boolean isSelectable;
100 }
101
102 private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
103 private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
104
105 Drawable mDivider;
106 int mDividerHeight;
Adam Cohenfb603862010-12-17 12:03:17 -0800107
Adam Powell637d3372010-08-25 14:37:03 -0700108 Drawable mOverScrollHeader;
109 Drawable mOverScrollFooter;
110
Romain Guy24443ea2009-05-11 11:56:30 -0700111 private boolean mIsCacheColorOpaque;
112 private boolean mDividerIsOpaque;
Romain Guy24443ea2009-05-11 11:56:30 -0700113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 private boolean mHeaderDividersEnabled;
115 private boolean mFooterDividersEnabled;
116
117 private boolean mAreAllItemsSelectable = true;
118
119 private boolean mItemsCanFocus = false;
120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 // used for temporary calculations.
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700122 private final Rect mTempRect = new Rect();
Romain Guya02903f2009-05-23 13:26:46 -0700123 private Paint mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124
125 // the single allocated result per list view; kinda cheesey but avoids
126 // allocating these thingies too often.
Romain Guy9c3184cc2010-02-25 17:32:54 -0800127 private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128
Adam Powell9bf3c122010-02-26 11:32:07 -0800129 // Keeps focused children visible through resizes
130 private FocusSelector mFocusSelector;
Adam Powell8350f7d2010-07-28 14:27:28 -0700131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 public ListView(Context context) {
133 this(context, null);
134 }
135
136 public ListView(Context context, AttributeSet attrs) {
137 this(context, attrs, com.android.internal.R.attr.listViewStyle);
138 }
139
140 public ListView(Context context, AttributeSet attrs, int defStyle) {
141 super(context, attrs, defStyle);
142
143 TypedArray a = context.obtainStyledAttributes(attrs,
144 com.android.internal.R.styleable.ListView, defStyle, 0);
145
146 CharSequence[] entries = a.getTextArray(
147 com.android.internal.R.styleable.ListView_entries);
148 if (entries != null) {
149 setAdapter(new ArrayAdapter<CharSequence>(context,
150 com.android.internal.R.layout.simple_list_item_1, entries));
151 }
152
153 final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
154 if (d != null) {
155 // If a divider is specified use its intrinsic height for divider height
156 setDivider(d);
157 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -0800158
Adam Powell637d3372010-08-25 14:37:03 -0700159 final Drawable osHeader = a.getDrawable(
160 com.android.internal.R.styleable.ListView_overScrollHeader);
161 if (osHeader != null) {
162 setOverscrollHeader(osHeader);
163 }
164
165 final Drawable osFooter = a.getDrawable(
166 com.android.internal.R.styleable.ListView_overScrollFooter);
167 if (osFooter != null) {
168 setOverscrollFooter(osFooter);
169 }
170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 // Use the height specified, zero being the default
172 final int dividerHeight = a.getDimensionPixelSize(
173 com.android.internal.R.styleable.ListView_dividerHeight, 0);
174 if (dividerHeight != 0) {
175 setDividerHeight(dividerHeight);
176 }
177
178 mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
179 mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
180
181 a.recycle();
182 }
183
184 /**
185 * @return The maximum amount a list view will scroll in response to
186 * an arrow event.
187 */
188 public int getMaxScrollAmount() {
189 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
190 }
191
192 /**
193 * Make sure views are touching the top or bottom edge, as appropriate for
194 * our gravity
195 */
196 private void adjustViewsUpOrDown() {
197 final int childCount = getChildCount();
198 int delta;
199
200 if (childCount > 0) {
201 View child;
202
203 if (!mStackFromBottom) {
204 // Uh-oh -- we came up short. Slide all views up to make them
205 // align with the top
206 child = getChildAt(0);
207 delta = child.getTop() - mListPadding.top;
208 if (mFirstPosition != 0) {
209 // It's OK to have some space above the first item if it is
210 // part of the vertical spacing
211 delta -= mDividerHeight;
212 }
213 if (delta < 0) {
214 // We only are looking to see if we are too low, not too high
215 delta = 0;
216 }
217 } else {
218 // we are too high, slide all views down to align with bottom
219 child = getChildAt(childCount - 1);
220 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
221
222 if (mFirstPosition + childCount < mItemCount) {
223 // It's OK to have some space below the last item if it is
224 // part of the vertical spacing
225 delta += mDividerHeight;
226 }
227
228 if (delta > 0) {
229 delta = 0;
230 }
231 }
232
233 if (delta != 0) {
234 offsetChildrenTopAndBottom(-delta);
235 }
236 }
237 }
238
239 /**
240 * Add a fixed view to appear at the top of the list. If addHeaderView is
241 * called more than once, the views will appear in the order they were
242 * added. Views added using this call can take focus if they want.
243 * <p>
244 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800245 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 * views.
247 *
248 * @param v The view to add.
249 * @param data Data to associate with this view
250 * @param isSelectable whether the item is selectable
251 */
252 public void addHeaderView(View v, Object data, boolean isSelectable) {
253
254 if (mAdapter != null) {
255 throw new IllegalStateException(
256 "Cannot add header view to list -- setAdapter has already been called.");
257 }
258
259 FixedViewInfo info = new FixedViewInfo();
260 info.view = v;
261 info.data = data;
262 info.isSelectable = isSelectable;
263 mHeaderViewInfos.add(info);
264 }
265
266 /**
267 * Add a fixed view to appear at the top of the list. If addHeaderView is
268 * called more than once, the views will appear in the order they were
269 * added. Views added using this call can take focus if they want.
270 * <p>
271 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800272 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 * views.
274 *
275 * @param v The view to add.
276 */
277 public void addHeaderView(View v) {
278 addHeaderView(v, null, true);
279 }
280
281 @Override
282 public int getHeaderViewsCount() {
283 return mHeaderViewInfos.size();
284 }
285
286 /**
287 * Removes a previously-added header view.
288 *
289 * @param v The view to remove
290 * @return true if the view was removed, false if the view was not a header
291 * view
292 */
293 public boolean removeHeaderView(View v) {
294 if (mHeaderViewInfos.size() > 0) {
295 boolean result = false;
296 if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
297 mDataSetObserver.onChanged();
298 result = true;
299 }
300 removeFixedViewInfo(v, mHeaderViewInfos);
301 return result;
302 }
303 return false;
304 }
305
306 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
307 int len = where.size();
308 for (int i = 0; i < len; ++i) {
309 FixedViewInfo info = where.get(i);
310 if (info.view == v) {
311 where.remove(i);
312 break;
313 }
314 }
315 }
316
317 /**
318 * Add a fixed view to appear at the bottom of the list. If addFooterView is
319 * called more than once, the views will appear in the order they were
320 * added. Views added using this call can take focus if they want.
321 * <p>
322 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800323 * the supplied cursor with one that will also account for header and footer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 * views.
325 *
326 * @param v The view to add.
327 * @param data Data to associate with this view
328 * @param isSelectable true if the footer view can be selected
329 */
330 public void addFooterView(View v, Object data, boolean isSelectable) {
331 FixedViewInfo info = new FixedViewInfo();
332 info.view = v;
333 info.data = data;
334 info.isSelectable = isSelectable;
335 mFooterViewInfos.add(info);
336
337 // in the case of re-adding a footer view, or adding one later on,
338 // we need to notify the observer
339 if (mDataSetObserver != null) {
340 mDataSetObserver.onChanged();
341 }
342 }
343
344 /**
345 * Add a fixed view to appear at the bottom of the list. If addFooterView is called more
346 * than once, the views will appear in the order they were added. Views added using
347 * this call can take focus if they want.
348 * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
Gilles Debunne176f9fc2010-03-02 11:00:50 -0800349 * cursor with one that will also account for header and footer views.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 *
351 *
352 * @param v The view to add.
353 */
354 public void addFooterView(View v) {
355 addFooterView(v, null, true);
356 }
357
358 @Override
359 public int getFooterViewsCount() {
360 return mFooterViewInfos.size();
361 }
362
363 /**
364 * Removes a previously-added footer view.
365 *
366 * @param v The view to remove
367 * @return
368 * true if the view was removed, false if the view was not a footer view
369 */
370 public boolean removeFooterView(View v) {
371 if (mFooterViewInfos.size() > 0) {
372 boolean result = false;
373 if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
374 mDataSetObserver.onChanged();
375 result = true;
376 }
377 removeFixedViewInfo(v, mFooterViewInfos);
378 return result;
379 }
380 return false;
381 }
382
383 /**
384 * Returns the adapter currently in use in this ListView. The returned adapter
385 * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
386 * might be a {@link WrapperListAdapter}.
387 *
388 * @return The adapter currently used to display data in this ListView.
389 *
390 * @see #setAdapter(ListAdapter)
391 */
392 @Override
393 public ListAdapter getAdapter() {
394 return mAdapter;
395 }
396
397 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700398 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
399 * through the specified intent.
400 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
401 */
402 @android.view.RemotableViewMethod
403 public void setRemoteViewsAdapter(Intent intent) {
404 super.setRemoteViewsAdapter(intent);
405 }
406
407 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 * Sets the data behind this ListView.
409 *
410 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
411 * depending on the ListView features currently in use. For instance, adding
412 * headers and/or footers will cause the adapter to be wrapped.
413 *
414 * @param adapter The ListAdapter which is responsible for maintaining the
415 * data backing this list and for producing a view to represent an
416 * item in that data set.
417 *
418 * @see #getAdapter()
419 */
420 @Override
421 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700422 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 mAdapter.unregisterDataSetObserver(mDataSetObserver);
424 }
425
426 resetList();
427 mRecycler.clear();
428
429 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
430 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
431 } else {
432 mAdapter = adapter;
433 }
434
435 mOldSelectedPosition = INVALID_POSITION;
436 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700437
438 // AbsListView#setAdapter will update choice mode states.
439 super.setAdapter(adapter);
440
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 if (mAdapter != null) {
442 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
443 mOldItemCount = mItemCount;
444 mItemCount = mAdapter.getCount();
445 checkFocus();
446
447 mDataSetObserver = new AdapterDataSetObserver();
448 mAdapter.registerDataSetObserver(mDataSetObserver);
449
450 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
451
452 int position;
453 if (mStackFromBottom) {
454 position = lookForSelectablePosition(mItemCount - 1, false);
455 } else {
456 position = lookForSelectablePosition(0, true);
457 }
458 setSelectedPositionInt(position);
459 setNextSelectedPositionInt(position);
460
461 if (mItemCount == 0) {
462 // Nothing selected
463 checkSelectionChanged();
464 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 } else {
466 mAreAllItemsSelectable = true;
467 checkFocus();
468 // Nothing selected
469 checkSelectionChanged();
470 }
471
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 requestLayout();
473 }
474
475
476 /**
477 * The list is empty. Clear everything out.
478 */
479 @Override
480 void resetList() {
Romain Guy2e447d42009-04-28 18:01:24 -0700481 // The parent's resetList() will remove all views from the layout so we need to
482 // cleanup the state of our footers and headers
483 clearRecycledState(mHeaderViewInfos);
484 clearRecycledState(mFooterViewInfos);
485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 super.resetList();
Romain Guy2e447d42009-04-28 18:01:24 -0700487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 mLayoutMode = LAYOUT_NORMAL;
489 }
490
Romain Guy2e447d42009-04-28 18:01:24 -0700491 private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
492 if (infos != null) {
493 final int count = infos.size();
494
495 for (int i = 0; i < count; i++) {
496 final View child = infos.get(i).view;
497 final LayoutParams p = (LayoutParams) child.getLayoutParams();
498 if (p != null) {
499 p.recycledHeaderFooter = false;
500 }
501 }
502 }
503 }
504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 /**
506 * @return Whether the list needs to show the top fading edge
507 */
508 private boolean showingTopFadingEdge() {
509 final int listTop = mScrollY + mListPadding.top;
510 return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
511 }
512
513 /**
514 * @return Whether the list needs to show the bottom fading edge
515 */
516 private boolean showingBottomFadingEdge() {
517 final int childCount = getChildCount();
518 final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
519 final int lastVisiblePosition = mFirstPosition + childCount - 1;
520
521 final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
522
523 return (lastVisiblePosition < mItemCount - 1)
524 || (bottomOfBottomChild < listBottom);
525 }
526
527
528 @Override
529 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
530
531 int rectTopWithinChild = rect.top;
532
533 // offset so rect is in coordinates of the this view
534 rect.offset(child.getLeft(), child.getTop());
535 rect.offset(-child.getScrollX(), -child.getScrollY());
536
537 final int height = getHeight();
538 int listUnfadedTop = getScrollY();
539 int listUnfadedBottom = listUnfadedTop + height;
540 final int fadingEdge = getVerticalFadingEdgeLength();
541
542 if (showingTopFadingEdge()) {
543 // leave room for top fading edge as long as rect isn't at very top
544 if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
545 listUnfadedTop += fadingEdge;
546 }
547 }
548
549 int childCount = getChildCount();
550 int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
551
552 if (showingBottomFadingEdge()) {
553 // leave room for bottom fading edge as long as rect isn't at very bottom
554 if ((mSelectedPosition < mItemCount - 1)
555 || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
556 listUnfadedBottom -= fadingEdge;
557 }
558 }
559
560 int scrollYDelta = 0;
561
562 if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
563 // need to MOVE DOWN to get it in view: move down just enough so
564 // that the entire rectangle is in view (or at least the first
565 // screen size chunk).
566
567 if (rect.height() > height) {
568 // just enough to get screen size chunk on
569 scrollYDelta += (rect.top - listUnfadedTop);
570 } else {
571 // get entire rect at bottom of screen
572 scrollYDelta += (rect.bottom - listUnfadedBottom);
573 }
574
575 // make sure we aren't scrolling beyond the end of our children
576 int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
577 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
578 } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
579 // need to MOVE UP to get it in view: move up just enough so that
580 // entire rectangle is in view (or at least the first screen
581 // size chunk of it).
582
583 if (rect.height() > height) {
584 // screen size chunk
585 scrollYDelta -= (listUnfadedBottom - rect.bottom);
586 } else {
587 // entire rect at top
588 scrollYDelta -= (listUnfadedTop - rect.top);
589 }
590
591 // make sure we aren't scrolling any further than the top our children
592 int top = getChildAt(0).getTop();
593 int deltaToTop = top - listUnfadedTop;
594 scrollYDelta = Math.max(scrollYDelta, deltaToTop);
595 }
596
597 final boolean scroll = scrollYDelta != 0;
598 if (scroll) {
599 scrollListItemsBy(-scrollYDelta);
Dianne Hackborn079e2352010-10-18 17:02:43 -0700600 positionSelector(INVALID_POSITION, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 mSelectedTop = child.getTop();
602 invalidate();
603 }
604 return scroll;
605 }
606
607 /**
608 * {@inheritDoc}
609 */
610 @Override
611 void fillGap(boolean down) {
612 final int count = getChildCount();
613 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800614 int paddingTop = 0;
615 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
616 paddingTop = getListPaddingTop();
617 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800619 paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 fillDown(mFirstPosition + count, startOffset);
621 correctTooHigh(getChildCount());
622 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800623 int paddingBottom = 0;
624 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
625 paddingBottom = getListPaddingBottom();
626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800628 getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 fillUp(mFirstPosition - 1, startOffset);
630 correctTooLow(getChildCount());
631 }
632 }
633
634 /**
635 * Fills the list from pos down to the end of the list view.
636 *
637 * @param pos The first position to put in the list
638 *
639 * @param nextTop The location where the top of the item associated with pos
640 * should be drawn
641 *
642 * @return The view that is currently selected, if it happens to be in the
643 * range that we draw.
644 */
645 private View fillDown(int pos, int nextTop) {
646 View selectedView = null;
647
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800648 int end = (mBottom - mTop);
649 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
650 end -= mListPadding.bottom;
651 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652
653 while (nextTop < end && pos < mItemCount) {
654 // is this the selected item?
655 boolean selected = pos == mSelectedPosition;
656 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
657
658 nextTop = child.getBottom() + mDividerHeight;
659 if (selected) {
660 selectedView = child;
661 }
662 pos++;
663 }
664
665 return selectedView;
666 }
667
668 /**
669 * Fills the list from pos up to the top of the list view.
670 *
671 * @param pos The first position to put in the list
672 *
673 * @param nextBottom The location where the bottom of the item associated
674 * with pos should be drawn
675 *
676 * @return The view that is currently selected
677 */
678 private View fillUp(int pos, int nextBottom) {
679 View selectedView = null;
680
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800681 int end = 0;
682 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
683 end = mListPadding.top;
684 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800685
686 while (nextBottom > end && pos >= 0) {
687 // is this the selected item?
688 boolean selected = pos == mSelectedPosition;
689 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
690 nextBottom = child.getTop() - mDividerHeight;
691 if (selected) {
692 selectedView = child;
693 }
694 pos--;
695 }
696
697 mFirstPosition = pos + 1;
698
699 return selectedView;
700 }
701
702 /**
703 * Fills the list from top to bottom, starting with mFirstPosition
704 *
705 * @param nextTop The location where the top of the first item should be
706 * drawn
707 *
708 * @return The view that is currently selected
709 */
710 private View fillFromTop(int nextTop) {
711 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
712 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
713 if (mFirstPosition < 0) {
714 mFirstPosition = 0;
715 }
716 return fillDown(mFirstPosition, nextTop);
717 }
718
719
720 /**
721 * Put mSelectedPosition in the middle of the screen and then build up and
722 * down from there. This method forces mSelectedPosition to the center.
723 *
724 * @param childrenTop Top of the area in which children can be drawn, as
725 * measured in pixels
726 * @param childrenBottom Bottom of the area in which children can be drawn,
727 * as measured in pixels
728 * @return Currently selected view
729 */
730 private View fillFromMiddle(int childrenTop, int childrenBottom) {
731 int height = childrenBottom - childrenTop;
732
733 int position = reconcileSelectedPosition();
734
735 View sel = makeAndAddView(position, childrenTop, true,
736 mListPadding.left, true);
737 mFirstPosition = position;
738
739 int selHeight = sel.getMeasuredHeight();
740 if (selHeight <= height) {
741 sel.offsetTopAndBottom((height - selHeight) / 2);
742 }
743
744 fillAboveAndBelow(sel, position);
745
746 if (!mStackFromBottom) {
747 correctTooHigh(getChildCount());
748 } else {
749 correctTooLow(getChildCount());
750 }
751
752 return sel;
753 }
754
755 /**
756 * Once the selected view as been placed, fill up the visible area above and
757 * below it.
758 *
759 * @param sel The selected view
760 * @param position The position corresponding to sel
761 */
762 private void fillAboveAndBelow(View sel, int position) {
763 final int dividerHeight = mDividerHeight;
764 if (!mStackFromBottom) {
765 fillUp(position - 1, sel.getTop() - dividerHeight);
766 adjustViewsUpOrDown();
767 fillDown(position + 1, sel.getBottom() + dividerHeight);
768 } else {
769 fillDown(position + 1, sel.getBottom() + dividerHeight);
770 adjustViewsUpOrDown();
771 fillUp(position - 1, sel.getTop() - dividerHeight);
772 }
773 }
774
775
776 /**
777 * Fills the grid based on positioning the new selection at a specific
778 * location. The selection may be moved so that it does not intersect the
779 * faded edges. The grid is then filled upwards and downwards from there.
780 *
781 * @param selectedTop Where the selected item should be
782 * @param childrenTop Where to start drawing children
783 * @param childrenBottom Last pixel where children can be drawn
784 * @return The view that currently has selection
785 */
786 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
787 int fadingEdgeLength = getVerticalFadingEdgeLength();
788 final int selectedPosition = mSelectedPosition;
789
790 View sel;
791
792 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
793 selectedPosition);
794 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
795 selectedPosition);
796
797 sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
798
799
800 // Some of the newly selected item extends below the bottom of the list
801 if (sel.getBottom() > bottomSelectionPixel) {
802 // Find space available above the selection into which we can scroll
803 // upwards
804 final int spaceAbove = sel.getTop() - topSelectionPixel;
805
806 // Find space required to bring the bottom of the selected item
807 // fully into view
808 final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
809 final int offset = Math.min(spaceAbove, spaceBelow);
810
811 // Now offset the selected item to get it into view
812 sel.offsetTopAndBottom(-offset);
813 } else if (sel.getTop() < topSelectionPixel) {
814 // Find space required to bring the top of the selected item fully
815 // into view
816 final int spaceAbove = topSelectionPixel - sel.getTop();
817
818 // Find space available below the selection into which we can scroll
819 // downwards
820 final int spaceBelow = bottomSelectionPixel - sel.getBottom();
821 final int offset = Math.min(spaceAbove, spaceBelow);
822
823 // Offset the selected item to get it into view
824 sel.offsetTopAndBottom(offset);
825 }
826
827 // Fill in views above and below
828 fillAboveAndBelow(sel, selectedPosition);
829
830 if (!mStackFromBottom) {
831 correctTooHigh(getChildCount());
832 } else {
833 correctTooLow(getChildCount());
834 }
835
836 return sel;
837 }
838
839 /**
840 * Calculate the bottom-most pixel we can draw the selection into
841 *
842 * @param childrenBottom Bottom pixel were children can be drawn
843 * @param fadingEdgeLength Length of the fading edge in pixels, if present
844 * @param selectedPosition The position that will be selected
845 * @return The bottom-most pixel we can draw the selection into
846 */
847 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
848 int selectedPosition) {
849 int bottomSelectionPixel = childrenBottom;
850 if (selectedPosition != mItemCount - 1) {
851 bottomSelectionPixel -= fadingEdgeLength;
852 }
853 return bottomSelectionPixel;
854 }
855
856 /**
857 * Calculate the top-most pixel we can draw the selection into
858 *
859 * @param childrenTop Top pixel were children can be drawn
860 * @param fadingEdgeLength Length of the fading edge in pixels, if present
861 * @param selectedPosition The position that will be selected
862 * @return The top-most pixel we can draw the selection into
863 */
864 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
865 // first pixel we can draw the selection into
866 int topSelectionPixel = childrenTop;
867 if (selectedPosition > 0) {
868 topSelectionPixel += fadingEdgeLength;
869 }
870 return topSelectionPixel;
871 }
872
Winson Chung499cb9f2010-07-16 11:18:17 -0700873 /**
874 * Smoothly scroll to the specified adapter position. The view will
875 * scroll such that the indicated position is displayed.
876 * @param position Scroll to this adapter position.
877 */
878 @android.view.RemotableViewMethod
879 public void smoothScrollToPosition(int position) {
880 super.smoothScrollToPosition(position);
881 }
882
883 /**
884 * Smoothly scroll to the specified adapter position offset. The view will
885 * scroll such that the indicated position is displayed.
886 * @param offset The amount to offset from the adapter position to scroll to.
887 */
888 @android.view.RemotableViewMethod
889 public void smoothScrollByOffset(int offset) {
890 super.smoothScrollByOffset(offset);
891 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800892
893 /**
894 * Fills the list based on positioning the new selection relative to the old
895 * selection. The new selection will be placed at, above, or below the
896 * location of the new selection depending on how the selection is moving.
897 * The selection will then be pinned to the visible part of the screen,
898 * excluding the edges that are faded. The list is then filled upwards and
899 * downwards from there.
900 *
901 * @param oldSel The old selected view. Useful for trying to put the new
902 * selection in the same place
903 * @param newSel The view that is to become selected. Useful for trying to
904 * put the new selection in the same place
905 * @param delta Which way we are moving
906 * @param childrenTop Where to start drawing children
907 * @param childrenBottom Last pixel where children can be drawn
908 * @return The view that currently has selection
909 */
910 private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
911 int childrenBottom) {
912 int fadingEdgeLength = getVerticalFadingEdgeLength();
913 final int selectedPosition = mSelectedPosition;
914
915 View sel;
916
917 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
918 selectedPosition);
919 final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
920 selectedPosition);
921
922 if (delta > 0) {
923 /*
924 * Case 1: Scrolling down.
925 */
926
927 /*
928 * Before After
929 * | | | |
930 * +-------+ +-------+
931 * | A | | A |
932 * | 1 | => +-------+
933 * +-------+ | B |
934 * | B | | 2 |
935 * +-------+ +-------+
936 * | | | |
937 *
938 * Try to keep the top of the previously selected item where it was.
939 * oldSel = A
940 * sel = B
941 */
942
943 // Put oldSel (A) where it belongs
944 oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
945 mListPadding.left, false);
946
947 final int dividerHeight = mDividerHeight;
948
949 // Now put the new selection (B) below that
950 sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
951 mListPadding.left, true);
952
953 // Some of the newly selected item extends below the bottom of the list
954 if (sel.getBottom() > bottomSelectionPixel) {
955
956 // Find space available above the selection into which we can scroll upwards
957 int spaceAbove = sel.getTop() - topSelectionPixel;
958
959 // Find space required to bring the bottom of the selected item fully into view
960 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
961
962 // Don't scroll more than half the height of the list
963 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
964 int offset = Math.min(spaceAbove, spaceBelow);
965 offset = Math.min(offset, halfVerticalSpace);
966
967 // We placed oldSel, so offset that item
968 oldSel.offsetTopAndBottom(-offset);
969 // Now offset the selected item to get it into view
970 sel.offsetTopAndBottom(-offset);
971 }
972
973 // Fill in views above and below
974 if (!mStackFromBottom) {
975 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
976 adjustViewsUpOrDown();
977 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
978 } else {
979 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
980 adjustViewsUpOrDown();
981 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
982 }
983 } else if (delta < 0) {
984 /*
985 * Case 2: Scrolling up.
986 */
987
988 /*
989 * Before After
990 * | | | |
991 * +-------+ +-------+
992 * | A | | A |
993 * +-------+ => | 1 |
994 * | B | +-------+
995 * | 2 | | B |
996 * +-------+ +-------+
997 * | | | |
998 *
999 * Try to keep the top of the item about to become selected where it was.
1000 * newSel = A
1001 * olSel = B
1002 */
1003
1004 if (newSel != null) {
1005 // Try to position the top of newSel (A) where it was before it was selected
1006 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1007 true);
1008 } else {
1009 // If (A) was not on screen and so did not have a view, position
1010 // it above the oldSel (B)
1011 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1012 true);
1013 }
1014
1015 // Some of the newly selected item extends above the top of the list
1016 if (sel.getTop() < topSelectionPixel) {
1017 // Find space required to bring the top of the selected item fully into view
1018 int spaceAbove = topSelectionPixel - sel.getTop();
1019
1020 // Find space available below the selection into which we can scroll downwards
1021 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1022
1023 // Don't scroll more than half the height of the list
1024 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1025 int offset = Math.min(spaceAbove, spaceBelow);
1026 offset = Math.min(offset, halfVerticalSpace);
1027
1028 // Offset the selected item to get it into view
1029 sel.offsetTopAndBottom(offset);
1030 }
1031
1032 // Fill in views above and below
1033 fillAboveAndBelow(sel, selectedPosition);
1034 } else {
1035
1036 int oldTop = oldSel.getTop();
1037
1038 /*
1039 * Case 3: Staying still
1040 */
1041 sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1042
1043 // We're staying still...
1044 if (oldTop < childrenTop) {
1045 // ... but the top of the old selection was off screen.
1046 // (This can happen if the data changes size out from under us)
1047 int newBottom = sel.getBottom();
1048 if (newBottom < childrenTop + 20) {
1049 // Not enough visible -- bring it onscreen
1050 sel.offsetTopAndBottom(childrenTop - sel.getTop());
1051 }
1052 }
1053
1054 // Fill in views above and below
1055 fillAboveAndBelow(sel, selectedPosition);
1056 }
1057
1058 return sel;
1059 }
1060
Adam Powell9bf3c122010-02-26 11:32:07 -08001061 private class FocusSelector implements Runnable {
1062 private int mPosition;
1063 private int mPositionTop;
1064
1065 public FocusSelector setup(int position, int top) {
1066 mPosition = position;
1067 mPositionTop = top;
1068 return this;
1069 }
1070
1071 public void run() {
1072 setSelectionFromTop(mPosition, mPositionTop);
1073 }
1074 }
1075
1076 @Override
1077 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1078 if (getChildCount() > 0) {
1079 View focusedChild = getFocusedChild();
1080 if (focusedChild != null) {
1081 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1082 final int childBottom = focusedChild.getBottom();
1083 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1084 final int top = focusedChild.getTop() - offset;
1085 if (mFocusSelector == null) {
1086 mFocusSelector = new FocusSelector();
1087 }
1088 post(mFocusSelector.setup(childPosition, top));
1089 }
1090 }
1091 super.onSizeChanged(w, h, oldw, oldh);
1092 }
1093
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 @Override
1095 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1096 // Sets up mListPadding
1097 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1098
1099 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1100 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1101 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1102 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1103
1104 int childWidth = 0;
1105 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001106 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107
1108 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1109 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
1110 heightMode == MeasureSpec.UNSPECIFIED)) {
Romain Guy21875052010-01-06 18:48:08 -08001111 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112
1113 measureScrapChild(child, 0, widthMeasureSpec);
1114
1115 childWidth = child.getMeasuredWidth();
1116 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001117 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118
Romain Guy9c3184cc2010-02-25 17:32:54 -08001119 if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1120 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001121 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 }
1123 }
1124
1125 if (widthMode == MeasureSpec.UNSPECIFIED) {
1126 widthSize = mListPadding.left + mListPadding.right + childWidth +
1127 getVerticalScrollbarWidth();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001128 } else {
1129 widthSize |= (childState&MEASURED_STATE_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001130 }
1131
1132 if (heightMode == MeasureSpec.UNSPECIFIED) {
1133 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1134 getVerticalFadingEdgeLength() * 2;
1135 }
1136
1137 if (heightMode == MeasureSpec.AT_MOST) {
1138 // TODO: after first layout we should maybe start at the first visible position, not 0
1139 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1140 }
1141
Dianne Hackborn189ee182010-12-02 21:48:53 -08001142 setMeasuredDimension(widthSize , heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 mWidthMeasureSpec = widthMeasureSpec;
1144 }
1145
1146 private void measureScrapChild(View child, int position, int widthMeasureSpec) {
1147 LayoutParams p = (LayoutParams) child.getLayoutParams();
1148 if (p == null) {
Romain Guy980a9382010-01-08 15:06:28 -08001149 p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001151 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 }
1153 p.viewType = mAdapter.getItemViewType(position);
Romain Guy0bf88592010-03-02 13:38:44 -08001154 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155
1156 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
1157 mListPadding.left + mListPadding.right, p.width);
1158 int lpHeight = p.height;
1159 int childHeightSpec;
1160 if (lpHeight > 0) {
1161 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1162 } else {
1163 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1164 }
1165 child.measure(childWidthSpec, childHeightSpec);
1166 }
1167
1168 /**
1169 * @return True to recycle the views used to measure this ListView in
1170 * UNSPECIFIED/AT_MOST modes, false otherwise.
1171 * @hide
1172 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001173 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 protected boolean recycleOnMeasure() {
1175 return true;
1176 }
1177
1178 /**
1179 * Measures the height of the given range of children (inclusive) and
1180 * returns the height with this ListView's padding and divider heights
1181 * included. If maxHeight is provided, the measuring will stop when the
1182 * current height reaches maxHeight.
1183 *
1184 * @param widthMeasureSpec The width measure spec to be given to a child's
1185 * {@link View#measure(int, int)}.
1186 * @param startPosition The position of the first child to be shown.
1187 * @param endPosition The (inclusive) position of the last child to be
1188 * shown. Specify {@link #NO_POSITION} if the last child should be
1189 * the last available child from the adapter.
1190 * @param maxHeight The maximum height that will be returned (if all the
1191 * children don't fit in this value, this value will be
1192 * returned).
1193 * @param disallowPartialChildPosition In general, whether the returned
1194 * height should only contain entire children. This is more
1195 * powerful--it is the first inclusive position at which partial
1196 * children will not be allowed. Example: it looks nice to have
1197 * at least 3 completely visible children, and in portrait this
1198 * will most likely fit; but in landscape there could be times
1199 * when even 2 children can not be completely shown, so a value
1200 * of 2 (remember, inclusive) would be good (assuming
1201 * startPosition is 0).
1202 * @return The height of this ListView with the given children.
1203 */
1204 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
1205 final int maxHeight, int disallowPartialChildPosition) {
1206
1207 final ListAdapter adapter = mAdapter;
1208 if (adapter == null) {
1209 return mListPadding.top + mListPadding.bottom;
1210 }
1211
1212 // Include the padding of the list
1213 int returnedHeight = mListPadding.top + mListPadding.bottom;
1214 final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
1215 // The previous height value that was less than maxHeight and contained
1216 // no partial children
1217 int prevHeightWithoutPartialChild = 0;
1218 int i;
1219 View child;
1220
1221 // mItemCount - 1 since endPosition parameter is inclusive
1222 endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1223 final AbsListView.RecycleBin recycleBin = mRecycler;
1224 final boolean recyle = recycleOnMeasure();
Romain Guy21875052010-01-06 18:48:08 -08001225 final boolean[] isScrap = mIsScrap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226
1227 for (i = startPosition; i <= endPosition; ++i) {
Romain Guy21875052010-01-06 18:48:08 -08001228 child = obtainView(i, isScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229
1230 measureScrapChild(child, i, widthMeasureSpec);
1231
1232 if (i > 0) {
1233 // Count the divider for all but one child
1234 returnedHeight += dividerHeight;
1235 }
1236
1237 // Recycle the view before we possibly return from the method
Romain Guy9c3184cc2010-02-25 17:32:54 -08001238 if (recyle && recycleBin.shouldRecycleViewType(
1239 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001240 recycleBin.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001241 }
1242
1243 returnedHeight += child.getMeasuredHeight();
1244
1245 if (returnedHeight >= maxHeight) {
1246 // We went over, figure out which height to return. If returnedHeight > maxHeight,
1247 // then the i'th position did not fit completely.
1248 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1249 && (i > disallowPartialChildPosition) // We've past the min pos
1250 && (prevHeightWithoutPartialChild > 0) // We have a prev height
1251 && (returnedHeight != maxHeight) // i'th child did not fit completely
1252 ? prevHeightWithoutPartialChild
1253 : maxHeight;
1254 }
1255
1256 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1257 prevHeightWithoutPartialChild = returnedHeight;
1258 }
1259 }
1260
1261 // At this point, we went through the range of children, and they each
1262 // completely fit, so return the returnedHeight
1263 return returnedHeight;
1264 }
1265
1266 @Override
1267 int findMotionRow(int y) {
1268 int childCount = getChildCount();
1269 if (childCount > 0) {
Adam Powell84222e02010-03-11 13:42:57 -08001270 if (!mStackFromBottom) {
1271 for (int i = 0; i < childCount; i++) {
1272 View v = getChildAt(i);
1273 if (y <= v.getBottom()) {
1274 return mFirstPosition + i;
1275 }
1276 }
1277 } else {
1278 for (int i = childCount - 1; i >= 0; i--) {
1279 View v = getChildAt(i);
1280 if (y >= v.getTop()) {
1281 return mFirstPosition + i;
1282 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 }
1284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 }
1286 return INVALID_POSITION;
1287 }
1288
1289 /**
1290 * Put a specific item at a specific location on the screen and then build
1291 * up and down from there.
1292 *
1293 * @param position The reference view to use as the starting point
1294 * @param top Pixel offset from the top of this view to the top of the
1295 * reference view.
1296 *
1297 * @return The selected view, or null if the selected view is outside the
1298 * visible area.
1299 */
1300 private View fillSpecific(int position, int top) {
1301 boolean tempIsSelected = position == mSelectedPosition;
1302 View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1303 // Possibly changed again in fillUp if we add rows above this one.
1304 mFirstPosition = position;
1305
1306 View above;
1307 View below;
1308
1309 final int dividerHeight = mDividerHeight;
1310 if (!mStackFromBottom) {
1311 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1312 // This will correct for the top of the first view not touching the top of the list
1313 adjustViewsUpOrDown();
1314 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1315 int childCount = getChildCount();
1316 if (childCount > 0) {
1317 correctTooHigh(childCount);
1318 }
1319 } else {
1320 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1321 // This will correct for the bottom of the last view not touching the bottom of the list
1322 adjustViewsUpOrDown();
1323 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1324 int childCount = getChildCount();
1325 if (childCount > 0) {
1326 correctTooLow(childCount);
1327 }
1328 }
1329
1330 if (tempIsSelected) {
1331 return temp;
1332 } else if (above != null) {
1333 return above;
1334 } else {
1335 return below;
1336 }
1337 }
1338
1339 /**
1340 * Check if we have dragged the bottom of the list too high (we have pushed the
1341 * top element off the top of the screen when we did not need to). Correct by sliding
1342 * everything back down.
1343 *
1344 * @param childCount Number of children
1345 */
1346 private void correctTooHigh(int childCount) {
1347 // First see if the last item is visible. If it is not, it is OK for the
1348 // top of the list to be pushed up.
1349 int lastPosition = mFirstPosition + childCount - 1;
1350 if (lastPosition == mItemCount - 1 && childCount > 0) {
1351
1352 // Get the last child ...
1353 final View lastChild = getChildAt(childCount - 1);
1354
1355 // ... and its bottom edge
1356 final int lastBottom = lastChild.getBottom();
1357
1358 // This is bottom of our drawable area
1359 final int end = (mBottom - mTop) - mListPadding.bottom;
1360
1361 // This is how far the bottom edge of the last view is from the bottom of the
1362 // drawable area
1363 int bottomOffset = end - lastBottom;
1364 View firstChild = getChildAt(0);
1365 final int firstTop = firstChild.getTop();
1366
1367 // Make sure we are 1) Too high, and 2) Either there are more rows above the
1368 // first row or the first row is scrolled off the top of the drawable area
1369 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
1370 if (mFirstPosition == 0) {
1371 // Don't pull the top too far down
1372 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1373 }
1374 // Move everything down
1375 offsetChildrenTopAndBottom(bottomOffset);
1376 if (mFirstPosition > 0) {
1377 // Fill the gap that was opened above mFirstPosition with more rows, if
1378 // possible
1379 fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1380 // Close up the remaining gap
1381 adjustViewsUpOrDown();
1382 }
1383
1384 }
1385 }
1386 }
1387
1388 /**
1389 * Check if we have dragged the bottom of the list too low (we have pushed the
1390 * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1391 * everything back up.
1392 *
1393 * @param childCount Number of children
1394 */
1395 private void correctTooLow(int childCount) {
1396 // First see if the first item is visible. If it is not, it is OK for the
1397 // bottom of the list to be pushed down.
1398 if (mFirstPosition == 0 && childCount > 0) {
1399
1400 // Get the first child ...
1401 final View firstChild = getChildAt(0);
1402
1403 // ... and its top edge
1404 final int firstTop = firstChild.getTop();
1405
1406 // This is top of our drawable area
1407 final int start = mListPadding.top;
1408
1409 // This is bottom of our drawable area
1410 final int end = (mBottom - mTop) - mListPadding.bottom;
1411
1412 // This is how far the top edge of the first view is from the top of the
1413 // drawable area
1414 int topOffset = firstTop - start;
1415 View lastChild = getChildAt(childCount - 1);
1416 final int lastBottom = lastChild.getBottom();
1417 int lastPosition = mFirstPosition + childCount - 1;
1418
1419 // Make sure we are 1) Too low, and 2) Either there are more rows below the
1420 // last row or the last row is scrolled off the bottom of the drawable area
Romain Guy6198ae82009-08-31 17:45:55 -07001421 if (topOffset > 0) {
1422 if (lastPosition < mItemCount - 1 || lastBottom > end) {
1423 if (lastPosition == mItemCount - 1) {
1424 // Don't pull the bottom too far up
1425 topOffset = Math.min(topOffset, lastBottom - end);
1426 }
1427 // Move everything up
1428 offsetChildrenTopAndBottom(-topOffset);
1429 if (lastPosition < mItemCount - 1) {
1430 // Fill the gap that was opened below the last position with more rows, if
1431 // possible
1432 fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1433 // Close up the remaining gap
1434 adjustViewsUpOrDown();
1435 }
1436 } else if (lastPosition == mItemCount - 1) {
1437 adjustViewsUpOrDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 }
1439 }
1440 }
1441 }
1442
1443 @Override
1444 protected void layoutChildren() {
1445 final boolean blockLayoutRequests = mBlockLayoutRequests;
1446 if (!blockLayoutRequests) {
1447 mBlockLayoutRequests = true;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001448 } else {
1449 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001450 }
1451
1452 try {
1453 super.layoutChildren();
1454
1455 invalidate();
1456
1457 if (mAdapter == null) {
1458 resetList();
1459 invokeOnItemScrollListener();
1460 return;
1461 }
1462
1463 int childrenTop = mListPadding.top;
1464 int childrenBottom = mBottom - mTop - mListPadding.bottom;
1465
1466 int childCount = getChildCount();
Romain Guyead0d4d2009-12-08 17:33:53 -08001467 int index = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001468 int delta = 0;
1469
1470 View sel;
1471 View oldSel = null;
1472 View oldFirst = null;
1473 View newSel = null;
1474
1475 View focusLayoutRestoreView = null;
1476
1477 // Remember stuff we will need down below
1478 switch (mLayoutMode) {
1479 case LAYOUT_SET_SELECTION:
1480 index = mNextSelectedPosition - mFirstPosition;
1481 if (index >= 0 && index < childCount) {
1482 newSel = getChildAt(index);
1483 }
1484 break;
1485 case LAYOUT_FORCE_TOP:
1486 case LAYOUT_FORCE_BOTTOM:
1487 case LAYOUT_SPECIFIC:
1488 case LAYOUT_SYNC:
1489 break;
1490 case LAYOUT_MOVE_SELECTION:
1491 default:
1492 // Remember the previously selected view
1493 index = mSelectedPosition - mFirstPosition;
1494 if (index >= 0 && index < childCount) {
1495 oldSel = getChildAt(index);
1496 }
1497
1498 // Remember the previous first child
1499 oldFirst = getChildAt(0);
1500
1501 if (mNextSelectedPosition >= 0) {
1502 delta = mNextSelectedPosition - mSelectedPosition;
1503 }
1504
1505 // Caution: newSel might be null
1506 newSel = getChildAt(index + delta);
1507 }
1508
1509
1510 boolean dataChanged = mDataChanged;
1511 if (dataChanged) {
1512 handleDataChanged();
1513 }
1514
1515 // Handle the empty set by removing all views that are visible
1516 // and calling it a day
1517 if (mItemCount == 0) {
1518 resetList();
1519 invokeOnItemScrollListener();
1520 return;
Romain Guyb45f1242009-03-24 21:30:00 -07001521 } else if (mItemCount != mAdapter.getCount()) {
1522 throw new IllegalStateException("The content of the adapter has changed but "
1523 + "ListView did not receive a notification. Make sure the content of "
1524 + "your adapter is not modified from a background thread, but only "
Owen Lin3940f2d2009-08-13 15:21:16 +08001525 + "from the UI thread. [in ListView(" + getId() + ", " + getClass()
1526 + ") with Adapter(" + mAdapter.getClass() + ")]");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 }
1528
1529 setSelectedPositionInt(mNextSelectedPosition);
1530
1531 // Pull all children into the RecycleBin.
1532 // These views will be reused if possible
1533 final int firstPosition = mFirstPosition;
1534 final RecycleBin recycleBin = mRecycler;
1535
1536 // reset the focus restoration
1537 View focusLayoutRestoreDirectChild = null;
1538
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 // Don't put header or footer views into the Recycler. Those are
1540 // already cached in mHeaderViews;
1541 if (dataChanged) {
1542 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001543 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001544 if (ViewDebug.TRACE_RECYCLER) {
1545 ViewDebug.trace(getChildAt(i),
1546 ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
1547 }
1548 }
1549 } else {
1550 recycleBin.fillActiveViews(childCount, firstPosition);
1551 }
1552
1553 // take focus back to us temporarily to avoid the eventual
1554 // call to clear focus when removing the focused child below
1555 // from messing things up when ViewRoot assigns focus back
1556 // to someone else
1557 final View focusedChild = getFocusedChild();
1558 if (focusedChild != null) {
1559 // TODO: in some cases focusedChild.getParent() == null
1560
1561 // we can remember the focused view to restore after relayout if the
1562 // data hasn't changed, or if the focused position is a header or footer
1563 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001564 focusLayoutRestoreDirectChild = focusedChild;
1565 // remember the specific view that had focus
1566 focusLayoutRestoreView = findFocus();
1567 if (focusLayoutRestoreView != null) {
1568 // tell it we are going to mess with it
1569 focusLayoutRestoreView.onStartTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001570 }
1571 }
1572 requestFocus();
1573 }
1574
1575 // Clear out old views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 detachAllViewsFromParent();
1577
1578 switch (mLayoutMode) {
1579 case LAYOUT_SET_SELECTION:
1580 if (newSel != null) {
1581 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1582 } else {
1583 sel = fillFromMiddle(childrenTop, childrenBottom);
1584 }
1585 break;
1586 case LAYOUT_SYNC:
1587 sel = fillSpecific(mSyncPosition, mSpecificTop);
1588 break;
1589 case LAYOUT_FORCE_BOTTOM:
1590 sel = fillUp(mItemCount - 1, childrenBottom);
1591 adjustViewsUpOrDown();
1592 break;
1593 case LAYOUT_FORCE_TOP:
1594 mFirstPosition = 0;
1595 sel = fillFromTop(childrenTop);
1596 adjustViewsUpOrDown();
1597 break;
1598 case LAYOUT_SPECIFIC:
1599 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
1600 break;
1601 case LAYOUT_MOVE_SELECTION:
1602 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1603 break;
1604 default:
1605 if (childCount == 0) {
1606 if (!mStackFromBottom) {
1607 final int position = lookForSelectablePosition(0, true);
1608 setSelectedPositionInt(position);
1609 sel = fillFromTop(childrenTop);
1610 } else {
1611 final int position = lookForSelectablePosition(mItemCount - 1, false);
1612 setSelectedPositionInt(position);
1613 sel = fillUp(mItemCount - 1, childrenBottom);
1614 }
1615 } else {
1616 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1617 sel = fillSpecific(mSelectedPosition,
1618 oldSel == null ? childrenTop : oldSel.getTop());
1619 } else if (mFirstPosition < mItemCount) {
1620 sel = fillSpecific(mFirstPosition,
1621 oldFirst == null ? childrenTop : oldFirst.getTop());
1622 } else {
1623 sel = fillSpecific(0, childrenTop);
1624 }
1625 }
1626 break;
1627 }
1628
1629 // Flush any cached views that did not get reused above
1630 recycleBin.scrapActiveViews();
1631
1632 if (sel != null) {
Romain Guy3616a412009-09-15 13:50:37 -07001633 // the current selected item should get focus if items
1634 // are focusable
1635 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1636 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1637 focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1638 if (!focusWasTaken) {
1639 // selected item didn't take focus, fine, but still want
1640 // to make sure something else outside of the selected view
1641 // has focus
1642 final View focused = getFocusedChild();
1643 if (focused != null) {
1644 focused.clearFocus();
1645 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001646 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001647 } else {
1648 sel.setSelected(false);
1649 mSelectorRect.setEmpty();
1650 }
1651 } else {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001652 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001653 }
1654 mSelectedTop = sel.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 } else {
Romain Guy3616a412009-09-15 13:50:37 -07001656 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
1657 View child = getChildAt(mMotionPosition - mFirstPosition);
Dianne Hackborn079e2352010-10-18 17:02:43 -07001658 if (child != null) positionSelector(mMotionPosition, child);
Romain Guy3616a412009-09-15 13:50:37 -07001659 } else {
1660 mSelectedTop = 0;
1661 mSelectorRect.setEmpty();
1662 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001663
Romain Guy3616a412009-09-15 13:50:37 -07001664 // even if there is not selected position, we may need to restore
1665 // focus (i.e. something focusable in touch mode)
1666 if (hasFocus() && focusLayoutRestoreView != null) {
1667 focusLayoutRestoreView.requestFocus();
1668 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001669 }
1670
1671 // tell focus view we are done mucking with it, if it is still in
1672 // our view hierarchy.
1673 if (focusLayoutRestoreView != null
1674 && focusLayoutRestoreView.getWindowToken() != null) {
1675 focusLayoutRestoreView.onFinishTemporaryDetach();
1676 }
1677
1678 mLayoutMode = LAYOUT_NORMAL;
1679 mDataChanged = false;
1680 mNeedSync = false;
1681 setNextSelectedPositionInt(mSelectedPosition);
1682
1683 updateScrollIndicators();
1684
1685 if (mItemCount > 0) {
1686 checkSelectionChanged();
1687 }
1688
1689 invokeOnItemScrollListener();
1690 } finally {
1691 if (!blockLayoutRequests) {
1692 mBlockLayoutRequests = false;
1693 }
1694 }
1695 }
1696
1697 /**
1698 * @param child a direct child of this list.
1699 * @return Whether child is a header or footer view.
1700 */
1701 private boolean isDirectChildHeaderOrFooter(View child) {
1702
1703 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1704 final int numHeaders = headers.size();
1705 for (int i = 0; i < numHeaders; i++) {
1706 if (child == headers.get(i).view) {
1707 return true;
1708 }
1709 }
1710 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1711 final int numFooters = footers.size();
1712 for (int i = 0; i < numFooters; i++) {
1713 if (child == footers.get(i).view) {
1714 return true;
1715 }
1716 }
1717 return false;
1718 }
1719
1720 /**
1721 * Obtain the view and add it to our list of children. The view can be made
1722 * fresh, converted from an unused view, or used as is if it was in the
1723 * recycle bin.
1724 *
1725 * @param position Logical position in the list
1726 * @param y Top or bottom edge of the view to add
1727 * @param flow If flow is true, align top edge to y. If false, align bottom
1728 * edge to y.
1729 * @param childrenLeft Left edge where children should be positioned
1730 * @param selected Is this position selected?
1731 * @return View that was added
1732 */
1733 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1734 boolean selected) {
1735 View child;
1736
1737
1738 if (!mDataChanged) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001739 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740 child = mRecycler.getActiveView(position);
1741 if (child != null) {
1742 if (ViewDebug.TRACE_RECYCLER) {
1743 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
1744 position, getChildCount());
1745 }
1746
1747 // Found it -- we're using an existing child
1748 // This just needs to be positioned
1749 setupChild(child, position, y, flow, childrenLeft, selected, true);
1750
1751 return child;
1752 }
1753 }
1754
1755 // Make a new view for this position, or convert an unused view if possible
Romain Guy21875052010-01-06 18:48:08 -08001756 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001757
1758 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001759 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001760
1761 return child;
1762 }
1763
1764 /**
1765 * Add a view as a child and make sure it is measured (if necessary) and
1766 * positioned properly.
1767 *
1768 * @param child The view to add
1769 * @param position The position of this child
1770 * @param y The y position relative to which this view will be positioned
1771 * @param flowDown If true, align top edge to y. If false, align bottom
1772 * edge to y.
1773 * @param childrenLeft Left edge where children should be positioned
1774 * @param selected Is this position selected?
1775 * @param recycled Has this view been pulled from the recycle bin? If so it
1776 * does not need to be remeasured.
1777 */
1778 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1779 boolean selected, boolean recycled) {
1780 final boolean isSelected = selected && shouldShowSelector();
1781 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001782 final int mode = mTouchMode;
1783 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1784 mMotionPosition == position;
1785 final boolean updateChildPressed = isPressed != child.isPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001786 final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1787
1788 // Respect layout params that are already in the view. Otherwise make some up...
1789 // noinspection unchecked
1790 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1791 if (p == null) {
Romain Guy980a9382010-01-08 15:06:28 -08001792 p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001793 ViewGroup.LayoutParams.WRAP_CONTENT, 0);
1794 }
1795 p.viewType = mAdapter.getItemViewType(position);
1796
Romain Guy0bf88592010-03-02 13:38:44 -08001797 if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001798 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001799 attachViewToParent(child, flowDown ? -1 : 0, p);
1800 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001801 p.forceAdd = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001802 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1803 p.recycledHeaderFooter = true;
1804 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001805 addViewInLayout(child, flowDown ? -1 : 0, p, true);
1806 }
1807
1808 if (updateChildSelected) {
1809 child.setSelected(isSelected);
1810 }
1811
Romain Guy3616a412009-09-15 13:50:37 -07001812 if (updateChildPressed) {
1813 child.setPressed(isPressed);
1814 }
1815
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001816 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1817 if (child instanceof Checkable) {
1818 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001819 } else if (getContext().getApplicationInfo().targetSdkVersion
1820 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1821 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001822 }
1823 }
1824
1825 if (needToMeasure) {
1826 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
1827 mListPadding.left + mListPadding.right, p.width);
1828 int lpHeight = p.height;
1829 int childHeightSpec;
1830 if (lpHeight > 0) {
1831 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1832 } else {
1833 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1834 }
1835 child.measure(childWidthSpec, childHeightSpec);
1836 } else {
1837 cleanupLayoutState(child);
1838 }
1839
1840 final int w = child.getMeasuredWidth();
1841 final int h = child.getMeasuredHeight();
1842 final int childTop = flowDown ? y : y - h;
1843
1844 if (needToMeasure) {
1845 final int childRight = childrenLeft + w;
1846 final int childBottom = childTop + h;
1847 child.layout(childrenLeft, childTop, childRight, childBottom);
1848 } else {
1849 child.offsetLeftAndRight(childrenLeft - child.getLeft());
1850 child.offsetTopAndBottom(childTop - child.getTop());
1851 }
1852
1853 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1854 child.setDrawingCacheEnabled(true);
1855 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001856
1857 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1858 != position) {
1859 child.jumpDrawablesToCurrentState();
1860 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001861 }
1862
1863 @Override
1864 protected boolean canAnimate() {
1865 return super.canAnimate() && mItemCount > 0;
1866 }
1867
1868 /**
1869 * Sets the currently selected item. If in touch mode, the item will not be selected
1870 * but it will still be positioned appropriately. If the specified selection position
1871 * is less than 0, then the item at position 0 will be selected.
1872 *
1873 * @param position Index (starting at 0) of the data item to be selected.
1874 */
1875 @Override
1876 public void setSelection(int position) {
1877 setSelectionFromTop(position, 0);
1878 }
1879
1880 /**
1881 * Sets the selected item and positions the selection y pixels from the top edge
1882 * of the ListView. (If in touch mode, the item will not be selected but it will
1883 * still be positioned appropriately.)
1884 *
1885 * @param position Index (starting at 0) of the data item to be selected.
1886 * @param y The distance from the top edge of the ListView (plus padding) that the
1887 * item will be positioned.
1888 */
1889 public void setSelectionFromTop(int position, int y) {
1890 if (mAdapter == null) {
1891 return;
1892 }
1893
1894 if (!isInTouchMode()) {
1895 position = lookForSelectablePosition(position, true);
1896 if (position >= 0) {
1897 setNextSelectedPositionInt(position);
1898 }
1899 } else {
1900 mResurrectToPosition = position;
1901 }
1902
1903 if (position >= 0) {
1904 mLayoutMode = LAYOUT_SPECIFIC;
1905 mSpecificTop = mListPadding.top + y;
1906
1907 if (mNeedSync) {
1908 mSyncPosition = position;
1909 mSyncRowId = mAdapter.getItemId(position);
1910 }
1911
1912 requestLayout();
1913 }
1914 }
1915
1916 /**
1917 * Makes the item at the supplied position selected.
Mike Cleronf116bf82009-09-27 19:14:12 -07001918 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001919 * @param position the position of the item to select
1920 */
1921 @Override
1922 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001923 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07001924 boolean awakeScrollbars = false;
1925
1926 final int selectedPosition = mSelectedPosition;
1927
1928 if (selectedPosition >= 0) {
1929 if (position == selectedPosition - 1) {
1930 awakeScrollbars = true;
1931 } else if (position == selectedPosition + 1) {
1932 awakeScrollbars = true;
1933 }
1934 }
1935
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001936 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07001937
1938 if (awakeScrollbars) {
1939 awakenScrollBars();
1940 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001941 }
1942
1943 /**
1944 * Find a position that can be selected (i.e., is not a separator).
1945 *
1946 * @param position The starting position to look at.
1947 * @param lookDown Whether to look down for other positions.
1948 * @return The next selectable position starting at position and then searching either up or
1949 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
1950 */
1951 @Override
1952 int lookForSelectablePosition(int position, boolean lookDown) {
1953 final ListAdapter adapter = mAdapter;
1954 if (adapter == null || isInTouchMode()) {
1955 return INVALID_POSITION;
1956 }
1957
1958 final int count = adapter.getCount();
1959 if (!mAreAllItemsSelectable) {
1960 if (lookDown) {
1961 position = Math.max(0, position);
1962 while (position < count && !adapter.isEnabled(position)) {
1963 position++;
1964 }
1965 } else {
1966 position = Math.min(position, count - 1);
1967 while (position >= 0 && !adapter.isEnabled(position)) {
1968 position--;
1969 }
1970 }
1971
1972 if (position < 0 || position >= count) {
1973 return INVALID_POSITION;
1974 }
1975 return position;
1976 } else {
1977 if (position < 0 || position >= count) {
1978 return INVALID_POSITION;
1979 }
1980 return position;
1981 }
1982 }
1983
svetoslavganov75986cf2009-05-14 22:28:01 -07001984 @Override
1985 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1986 boolean populated = super.dispatchPopulateAccessibilityEvent(event);
1987
Amith Yamasanid296faf2009-06-04 12:11:25 -07001988 // If the item count is less than 15 then subtract disabled items from the count and
1989 // position. Otherwise ignore disabled items.
svetoslavganov75986cf2009-05-14 22:28:01 -07001990 if (!populated) {
1991 int itemCount = 0;
1992 int currentItemIndex = getSelectedItemPosition();
1993
1994 ListAdapter adapter = getAdapter();
1995 if (adapter != null) {
Amith Yamasanid296faf2009-06-04 12:11:25 -07001996 final int count = adapter.getCount();
1997 if (count < 15) {
1998 for (int i = 0; i < count; i++) {
1999 if (adapter.isEnabled(i)) {
2000 itemCount++;
2001 } else if (i <= currentItemIndex) {
2002 currentItemIndex--;
2003 }
svetoslavganov75986cf2009-05-14 22:28:01 -07002004 }
Amith Yamasanid296faf2009-06-04 12:11:25 -07002005 } else {
2006 itemCount = count;
svetoslavganov75986cf2009-05-14 22:28:01 -07002007 }
2008 }
2009
2010 event.setItemCount(itemCount);
2011 event.setCurrentItemIndex(currentItemIndex);
2012 }
2013
2014 return populated;
2015 }
2016
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002017 /**
2018 * setSelectionAfterHeaderView set the selection to be the first list item
2019 * after the header views.
2020 */
2021 public void setSelectionAfterHeaderView() {
2022 final int count = mHeaderViewInfos.size();
2023 if (count > 0) {
2024 mNextSelectedPosition = 0;
2025 return;
2026 }
2027
2028 if (mAdapter != null) {
2029 setSelection(count);
2030 } else {
2031 mNextSelectedPosition = count;
2032 mLayoutMode = LAYOUT_SET_SELECTION;
2033 }
2034
2035 }
2036
2037 @Override
2038 public boolean dispatchKeyEvent(KeyEvent event) {
2039 // Dispatch in the normal way
2040 boolean handled = super.dispatchKeyEvent(event);
2041 if (!handled) {
2042 // If we didn't handle it...
2043 View focused = getFocusedChild();
2044 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2045 // ... and our focused child didn't handle it
2046 // ... give it to ourselves so we can scroll if necessary
2047 handled = onKeyDown(event.getKeyCode(), event);
2048 }
2049 }
2050 return handled;
2051 }
2052
2053 @Override
2054 public boolean onKeyDown(int keyCode, KeyEvent event) {
2055 return commonKey(keyCode, 1, event);
2056 }
2057
2058 @Override
2059 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2060 return commonKey(keyCode, repeatCount, event);
2061 }
2062
2063 @Override
2064 public boolean onKeyUp(int keyCode, KeyEvent event) {
2065 return commonKey(keyCode, 1, event);
2066 }
2067
2068 private boolean commonKey(int keyCode, int count, KeyEvent event) {
2069 if (mAdapter == null) {
2070 return false;
2071 }
2072
2073 if (mDataChanged) {
2074 layoutChildren();
2075 }
2076
2077 boolean handled = false;
2078 int action = event.getAction();
2079
2080 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002081 switch (keyCode) {
2082 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002083 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002084 handled = resurrectSelectionIfNeeded();
2085 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002086 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002087 if (arrowScroll(FOCUS_UP)) {
2088 handled = true;
2089 } else {
2090 break;
2091 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002092 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002093 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002094 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002095 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002096 }
2097 break;
2098
2099 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002100 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002101 handled = resurrectSelectionIfNeeded();
2102 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002103 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002104 if (arrowScroll(FOCUS_DOWN)) {
2105 handled = true;
2106 } else {
2107 break;
2108 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002110 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002111 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002112 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002113 }
2114 break;
2115
2116 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002117 if (event.hasNoModifiers()) {
2118 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2119 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002120 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002122 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002123 if (event.hasNoModifiers()) {
2124 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2125 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002126 break;
2127
2128 case KeyEvent.KEYCODE_DPAD_CENTER:
2129 case KeyEvent.KEYCODE_ENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002130 if (event.hasNoModifiers()) {
2131 handled = resurrectSelectionIfNeeded();
2132 if (!handled
2133 && event.getRepeatCount() == 0 && getChildCount() > 0) {
2134 keyPressed();
2135 handled = true;
2136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002137 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002138 break;
2139
2140 case KeyEvent.KEYCODE_SPACE:
2141 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002142 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002143 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002144 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002145 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002146 }
2147 handled = true;
2148 }
2149 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002150
2151 case KeyEvent.KEYCODE_PAGE_UP:
2152 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002153 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002154 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002155 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002156 }
2157 break;
2158
2159 case KeyEvent.KEYCODE_PAGE_DOWN:
2160 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002161 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002162 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002163 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002164 }
2165 break;
2166
2167 case KeyEvent.KEYCODE_MOVE_HOME:
2168 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002169 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002170 }
2171 break;
2172
2173 case KeyEvent.KEYCODE_MOVE_END:
2174 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002175 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002176 }
2177 break;
2178
2179 case KeyEvent.KEYCODE_TAB:
2180 // XXX Sometimes it is useful to be able to TAB through the items in
2181 // a ListView sequentially. Unfortunately this can create an
2182 // asymmetry in TAB navigation order unless the list selection
2183 // always reverts to the top or bottom when receiving TAB focus from
2184 // another widget. Leaving this behavior disabled for now but
2185 // perhaps it should be configurable (and more comprehensive).
2186 if (false) {
2187 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002188 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002189 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002190 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002191 }
2192 }
2193 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002194 }
2195 }
2196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002197 if (handled) {
2198 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002201 if (sendToTextFilter(keyCode, count, event)) {
2202 return true;
2203 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002204
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002205 switch (action) {
2206 case KeyEvent.ACTION_DOWN:
2207 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002208
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002209 case KeyEvent.ACTION_UP:
2210 return super.onKeyUp(keyCode, event);
2211
2212 case KeyEvent.ACTION_MULTIPLE:
2213 return super.onKeyMultiple(keyCode, count, event);
2214
2215 default: // shouldn't happen
2216 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002217 }
2218 }
2219
2220 /**
2221 * Scrolls up or down by the number of items currently present on screen.
2222 *
2223 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2224 * @return whether selection was moved
2225 */
2226 boolean pageScroll(int direction) {
2227 int nextPage = -1;
2228 boolean down = false;
2229
2230 if (direction == FOCUS_UP) {
2231 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
2232 } else if (direction == FOCUS_DOWN) {
2233 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2234 down = true;
2235 }
2236
2237 if (nextPage >= 0) {
2238 int position = lookForSelectablePosition(nextPage, down);
2239 if (position >= 0) {
2240 mLayoutMode = LAYOUT_SPECIFIC;
2241 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2242
2243 if (down && position > mItemCount - getChildCount()) {
2244 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2245 }
2246
2247 if (!down && position < getChildCount()) {
2248 mLayoutMode = LAYOUT_FORCE_TOP;
2249 }
2250
2251 setSelectionInt(position);
2252 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002253 if (!awakenScrollBars()) {
2254 invalidate();
2255 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002256
2257 return true;
2258 }
2259 }
2260
2261 return false;
2262 }
2263
2264 /**
2265 * Go to the last or first item if possible (not worrying about panning across or navigating
2266 * within the internal focus of the currently selected item.)
2267 *
2268 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2269 *
2270 * @return whether selection was moved
2271 */
2272 boolean fullScroll(int direction) {
2273 boolean moved = false;
2274 if (direction == FOCUS_UP) {
2275 if (mSelectedPosition != 0) {
2276 int position = lookForSelectablePosition(0, true);
2277 if (position >= 0) {
2278 mLayoutMode = LAYOUT_FORCE_TOP;
2279 setSelectionInt(position);
2280 invokeOnItemScrollListener();
2281 }
2282 moved = true;
2283 }
2284 } else if (direction == FOCUS_DOWN) {
2285 if (mSelectedPosition < mItemCount - 1) {
2286 int position = lookForSelectablePosition(mItemCount - 1, true);
2287 if (position >= 0) {
2288 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2289 setSelectionInt(position);
2290 invokeOnItemScrollListener();
2291 }
2292 moved = true;
2293 }
2294 }
2295
Mike Cleronf116bf82009-09-27 19:14:12 -07002296 if (moved && !awakenScrollBars()) {
2297 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002298 invalidate();
2299 }
2300
2301 return moved;
2302 }
2303
2304 /**
2305 * To avoid horizontal focus searches changing the selected item, we
2306 * manually focus search within the selected item (as applicable), and
2307 * prevent focus from jumping to something within another item.
2308 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2309 * @return Whether this consumes the key event.
2310 */
2311 private boolean handleHorizontalFocusWithinListItem(int direction) {
2312 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002313 throw new IllegalArgumentException("direction must be one of"
2314 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002315 }
2316
2317 final int numChildren = getChildCount();
2318 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2319 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002320 if (selectedView != null && selectedView.hasFocus() &&
2321 selectedView instanceof ViewGroup) {
2322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 final View currentFocus = selectedView.findFocus();
2324 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002325 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002326 if (nextFocus != null) {
2327 // do the math to get interesting rect in next focus' coordinates
2328 currentFocus.getFocusedRect(mTempRect);
2329 offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2330 offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2331 if (nextFocus.requestFocus(direction, mTempRect)) {
2332 return true;
2333 }
2334 }
2335 // we are blocking the key from being handled (by returning true)
2336 // if the global result is going to be some other view within this
2337 // list. this is to acheive the overall goal of having
2338 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002339 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2340 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002341 if (globalNextFocus != null) {
2342 return isViewAncestorOf(globalNextFocus, this);
2343 }
2344 }
2345 }
2346 return false;
2347 }
2348
2349 /**
2350 * Scrolls to the next or previous item if possible.
2351 *
2352 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2353 *
2354 * @return whether selection was moved
2355 */
2356 boolean arrowScroll(int direction) {
2357 try {
2358 mInLayout = true;
2359 final boolean handled = arrowScrollImpl(direction);
2360 if (handled) {
2361 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2362 }
2363 return handled;
2364 } finally {
2365 mInLayout = false;
2366 }
2367 }
2368
2369 /**
2370 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2371 * whether there are focusable items etc.
2372 *
2373 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2374 * @return Whether any scrolling, selection or focus change occured.
2375 */
2376 private boolean arrowScrollImpl(int direction) {
2377 if (getChildCount() <= 0) {
2378 return false;
2379 }
2380
2381 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002382 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002383
2384 int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
2385 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2386
2387 // if we are moving focus, we may OVERRIDE the default behavior
2388 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2389 if (focusResult != null) {
2390 nextSelectedPosition = focusResult.getSelectedPosition();
2391 amountToScroll = focusResult.getAmountToScroll();
2392 }
2393
2394 boolean needToRedraw = focusResult != null;
2395 if (nextSelectedPosition != INVALID_POSITION) {
2396 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2397 setSelectedPositionInt(nextSelectedPosition);
2398 setNextSelectedPositionInt(nextSelectedPosition);
2399 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002400 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002401 if (mItemsCanFocus && focusResult == null) {
2402 // there was no new view found to take focus, make sure we
2403 // don't leave focus with the old selection
2404 final View focused = getFocusedChild();
2405 if (focused != null) {
2406 focused.clearFocus();
2407 }
2408 }
2409 needToRedraw = true;
2410 checkSelectionChanged();
2411 }
2412
2413 if (amountToScroll > 0) {
2414 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2415 needToRedraw = true;
2416 }
2417
2418 // if we didn't find a new focusable, make sure any existing focused
2419 // item that was panned off screen gives up focus.
2420 if (mItemsCanFocus && (focusResult == null)
2421 && selectedView != null && selectedView.hasFocus()) {
2422 final View focused = selectedView.findFocus();
2423 if (distanceToView(focused) > 0) {
2424 focused.clearFocus();
2425 }
2426 }
2427
2428 // if the current selection is panned off, we need to remove the selection
2429 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2430 && !isViewAncestorOf(selectedView, this)) {
2431 selectedView = null;
2432 hideSelector();
2433
2434 // but we don't want to set the ressurect position (that would make subsequent
2435 // unhandled key events bring back the item we just scrolled off!)
2436 mResurrectToPosition = INVALID_POSITION;
2437 }
2438
2439 if (needToRedraw) {
2440 if (selectedView != null) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07002441 positionSelector(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002442 mSelectedTop = selectedView.getTop();
2443 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002444 if (!awakenScrollBars()) {
2445 invalidate();
2446 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002447 invokeOnItemScrollListener();
2448 return true;
2449 }
2450
2451 return false;
2452 }
2453
2454 /**
2455 * When selection changes, it is possible that the previously selected or the
2456 * next selected item will change its size. If so, we need to offset some folks,
2457 * and re-layout the items as appropriate.
2458 *
2459 * @param selectedView The currently selected view (before changing selection).
2460 * should be <code>null</code> if there was no previous selection.
2461 * @param direction Either {@link android.view.View#FOCUS_UP} or
2462 * {@link android.view.View#FOCUS_DOWN}.
2463 * @param newSelectedPosition The position of the next selection.
2464 * @param newFocusAssigned whether new focus was assigned. This matters because
2465 * when something has focus, we don't want to show selection (ugh).
2466 */
2467 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2468 boolean newFocusAssigned) {
2469 if (newSelectedPosition == INVALID_POSITION) {
2470 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2471 }
2472
2473 // whether or not we are moving down or up, we want to preserve the
2474 // top of whatever view is on top:
2475 // - moving down: the view that had selection
2476 // - moving up: the view that is getting selection
2477 View topView;
2478 View bottomView;
2479 int topViewIndex, bottomViewIndex;
2480 boolean topSelected = false;
2481 final int selectedIndex = mSelectedPosition - mFirstPosition;
2482 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2483 if (direction == View.FOCUS_UP) {
2484 topViewIndex = nextSelectedIndex;
2485 bottomViewIndex = selectedIndex;
2486 topView = getChildAt(topViewIndex);
2487 bottomView = selectedView;
2488 topSelected = true;
2489 } else {
2490 topViewIndex = selectedIndex;
2491 bottomViewIndex = nextSelectedIndex;
2492 topView = selectedView;
2493 bottomView = getChildAt(bottomViewIndex);
2494 }
2495
2496 final int numChildren = getChildCount();
2497
2498 // start with top view: is it changing size?
2499 if (topView != null) {
2500 topView.setSelected(!newFocusAssigned && topSelected);
2501 measureAndAdjustDown(topView, topViewIndex, numChildren);
2502 }
2503
2504 // is the bottom view changing size?
2505 if (bottomView != null) {
2506 bottomView.setSelected(!newFocusAssigned && !topSelected);
2507 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2508 }
2509 }
2510
2511 /**
2512 * Re-measure a child, and if its height changes, lay it out preserving its
2513 * top, and adjust the children below it appropriately.
2514 * @param child The child
2515 * @param childIndex The view group index of the child.
2516 * @param numChildren The number of children in the view group.
2517 */
2518 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2519 int oldHeight = child.getHeight();
2520 measureItem(child);
2521 if (child.getMeasuredHeight() != oldHeight) {
2522 // lay out the view, preserving its top
2523 relayoutMeasuredItem(child);
2524
2525 // adjust views below appropriately
2526 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2527 for (int i = childIndex + 1; i < numChildren; i++) {
2528 getChildAt(i).offsetTopAndBottom(heightDelta);
2529 }
2530 }
2531 }
2532
2533 /**
2534 * Measure a particular list child.
2535 * TODO: unify with setUpChild.
2536 * @param child The child.
2537 */
2538 private void measureItem(View child) {
2539 ViewGroup.LayoutParams p = child.getLayoutParams();
2540 if (p == null) {
2541 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002542 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002543 ViewGroup.LayoutParams.WRAP_CONTENT);
2544 }
2545
2546 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2547 mListPadding.left + mListPadding.right, p.width);
2548 int lpHeight = p.height;
2549 int childHeightSpec;
2550 if (lpHeight > 0) {
2551 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2552 } else {
2553 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2554 }
2555 child.measure(childWidthSpec, childHeightSpec);
2556 }
2557
2558 /**
2559 * Layout a child that has been measured, preserving its top position.
2560 * TODO: unify with setUpChild.
2561 * @param child The child.
2562 */
2563 private void relayoutMeasuredItem(View child) {
2564 final int w = child.getMeasuredWidth();
2565 final int h = child.getMeasuredHeight();
2566 final int childLeft = mListPadding.left;
2567 final int childRight = childLeft + w;
2568 final int childTop = child.getTop();
2569 final int childBottom = childTop + h;
2570 child.layout(childLeft, childTop, childRight, childBottom);
2571 }
2572
2573 /**
2574 * @return The amount to preview next items when arrow srolling.
2575 */
2576 private int getArrowScrollPreviewLength() {
2577 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2578 }
2579
2580 /**
2581 * Determine how much we need to scroll in order to get the next selected view
2582 * visible, with a fading edge showing below as applicable. The amount is
2583 * capped at {@link #getMaxScrollAmount()} .
2584 *
2585 * @param direction either {@link android.view.View#FOCUS_UP} or
2586 * {@link android.view.View#FOCUS_DOWN}.
2587 * @param nextSelectedPosition The position of the next selection, or
2588 * {@link #INVALID_POSITION} if there is no next selectable position
2589 * @return The amount to scroll. Note: this is always positive! Direction
2590 * needs to be taken into account when actually scrolling.
2591 */
2592 private int amountToScroll(int direction, int nextSelectedPosition) {
2593 final int listBottom = getHeight() - mListPadding.bottom;
2594 final int listTop = mListPadding.top;
2595
2596 final int numChildren = getChildCount();
2597
2598 if (direction == View.FOCUS_DOWN) {
2599 int indexToMakeVisible = numChildren - 1;
2600 if (nextSelectedPosition != INVALID_POSITION) {
2601 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2602 }
2603
2604 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2605 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2606
2607 int goalBottom = listBottom;
2608 if (positionToMakeVisible < mItemCount - 1) {
2609 goalBottom -= getArrowScrollPreviewLength();
2610 }
2611
2612 if (viewToMakeVisible.getBottom() <= goalBottom) {
2613 // item is fully visible.
2614 return 0;
2615 }
2616
2617 if (nextSelectedPosition != INVALID_POSITION
2618 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2619 // item already has enough of it visible, changing selection is good enough
2620 return 0;
2621 }
2622
2623 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2624
2625 if ((mFirstPosition + numChildren) == mItemCount) {
2626 // last is last in list -> make sure we don't scroll past it
2627 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2628 amountToScroll = Math.min(amountToScroll, max);
2629 }
2630
2631 return Math.min(amountToScroll, getMaxScrollAmount());
2632 } else {
2633 int indexToMakeVisible = 0;
2634 if (nextSelectedPosition != INVALID_POSITION) {
2635 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2636 }
2637 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2638 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2639 int goalTop = listTop;
2640 if (positionToMakeVisible > 0) {
2641 goalTop += getArrowScrollPreviewLength();
2642 }
2643 if (viewToMakeVisible.getTop() >= goalTop) {
2644 // item is fully visible.
2645 return 0;
2646 }
2647
2648 if (nextSelectedPosition != INVALID_POSITION &&
2649 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2650 // item already has enough of it visible, changing selection is good enough
2651 return 0;
2652 }
2653
2654 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2655 if (mFirstPosition == 0) {
2656 // first is first in list -> make sure we don't scroll past it
2657 final int max = listTop - getChildAt(0).getTop();
2658 amountToScroll = Math.min(amountToScroll, max);
2659 }
2660 return Math.min(amountToScroll, getMaxScrollAmount());
2661 }
2662 }
2663
2664 /**
2665 * Holds results of focus aware arrow scrolling.
2666 */
2667 static private class ArrowScrollFocusResult {
2668 private int mSelectedPosition;
2669 private int mAmountToScroll;
2670
2671 /**
2672 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2673 */
2674 void populate(int selectedPosition, int amountToScroll) {
2675 mSelectedPosition = selectedPosition;
2676 mAmountToScroll = amountToScroll;
2677 }
2678
2679 public int getSelectedPosition() {
2680 return mSelectedPosition;
2681 }
2682
2683 public int getAmountToScroll() {
2684 return mAmountToScroll;
2685 }
2686 }
2687
2688 /**
2689 * @param direction either {@link android.view.View#FOCUS_UP} or
2690 * {@link android.view.View#FOCUS_DOWN}.
2691 * @return The position of the next selectable position of the views that
2692 * are currently visible, taking into account the fact that there might
2693 * be no selection. Returns {@link #INVALID_POSITION} if there is no
2694 * selectable view on screen in the given direction.
2695 */
2696 private int lookForSelectablePositionOnScreen(int direction) {
2697 final int firstPosition = mFirstPosition;
2698 if (direction == View.FOCUS_DOWN) {
2699 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2700 mSelectedPosition + 1 :
2701 firstPosition;
2702 if (startPos >= mAdapter.getCount()) {
2703 return INVALID_POSITION;
2704 }
2705 if (startPos < firstPosition) {
2706 startPos = firstPosition;
2707 }
2708
2709 final int lastVisiblePos = getLastVisiblePosition();
2710 final ListAdapter adapter = getAdapter();
2711 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2712 if (adapter.isEnabled(pos)
2713 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2714 return pos;
2715 }
2716 }
2717 } else {
2718 int last = firstPosition + getChildCount() - 1;
2719 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2720 mSelectedPosition - 1 :
2721 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08002722 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002723 return INVALID_POSITION;
2724 }
2725 if (startPos > last) {
2726 startPos = last;
2727 }
2728
2729 final ListAdapter adapter = getAdapter();
2730 for (int pos = startPos; pos >= firstPosition; pos--) {
2731 if (adapter.isEnabled(pos)
2732 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2733 return pos;
2734 }
2735 }
2736 }
2737 return INVALID_POSITION;
2738 }
2739
2740 /**
2741 * Do an arrow scroll based on focus searching. If a new view is
2742 * given focus, return the selection delta and amount to scroll via
2743 * an {@link ArrowScrollFocusResult}, otherwise, return null.
2744 *
2745 * @param direction either {@link android.view.View#FOCUS_UP} or
2746 * {@link android.view.View#FOCUS_DOWN}.
2747 * @return The result if focus has changed, or <code>null</code>.
2748 */
2749 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2750 final View selectedView = getSelectedView();
2751 View newFocus;
2752 if (selectedView != null && selectedView.hasFocus()) {
2753 View oldFocus = selectedView.findFocus();
2754 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2755 } else {
2756 if (direction == View.FOCUS_DOWN) {
2757 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2758 final int listTop = mListPadding.top +
2759 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2760 final int ySearchPoint =
2761 (selectedView != null && selectedView.getTop() > listTop) ?
2762 selectedView.getTop() :
2763 listTop;
2764 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2765 } else {
2766 final boolean bottomFadingEdgeShowing =
2767 (mFirstPosition + getChildCount() - 1) < mItemCount;
2768 final int listBottom = getHeight() - mListPadding.bottom -
2769 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2770 final int ySearchPoint =
2771 (selectedView != null && selectedView.getBottom() < listBottom) ?
2772 selectedView.getBottom() :
2773 listBottom;
2774 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2775 }
2776 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2777 }
2778
2779 if (newFocus != null) {
2780 final int positionOfNewFocus = positionOfNewFocus(newFocus);
2781
2782 // if the focus change is in a different new position, make sure
2783 // we aren't jumping over another selectable position
2784 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2785 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2786 if (selectablePosition != INVALID_POSITION &&
2787 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2788 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2789 return null;
2790 }
2791 }
2792
2793 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2794
2795 final int maxScrollAmount = getMaxScrollAmount();
2796 if (focusScroll < maxScrollAmount) {
2797 // not moving too far, safe to give next view focus
2798 newFocus.requestFocus(direction);
2799 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2800 return mArrowScrollFocusResult;
2801 } else if (distanceToView(newFocus) < maxScrollAmount){
2802 // Case to consider:
2803 // too far to get entire next focusable on screen, but by going
2804 // max scroll amount, we are getting it at least partially in view,
2805 // so give it focus and scroll the max ammount.
2806 newFocus.requestFocus(direction);
2807 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2808 return mArrowScrollFocusResult;
2809 }
2810 }
2811 return null;
2812 }
2813
2814 /**
2815 * @param newFocus The view that would have focus.
2816 * @return the position that contains newFocus
2817 */
2818 private int positionOfNewFocus(View newFocus) {
2819 final int numChildren = getChildCount();
2820 for (int i = 0; i < numChildren; i++) {
2821 final View child = getChildAt(i);
2822 if (isViewAncestorOf(newFocus, child)) {
2823 return mFirstPosition + i;
2824 }
2825 }
2826 throw new IllegalArgumentException("newFocus is not a child of any of the"
2827 + " children of the list!");
2828 }
2829
2830 /**
2831 * Return true if child is an ancestor of parent, (or equal to the parent).
2832 */
2833 private boolean isViewAncestorOf(View child, View parent) {
2834 if (child == parent) {
2835 return true;
2836 }
2837
2838 final ViewParent theParent = child.getParent();
2839 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2840 }
2841
2842 /**
2843 * Determine how much we need to scroll in order to get newFocus in view.
2844 * @param direction either {@link android.view.View#FOCUS_UP} or
2845 * {@link android.view.View#FOCUS_DOWN}.
2846 * @param newFocus The view that would take focus.
2847 * @param positionOfNewFocus The position of the list item containing newFocus
2848 * @return The amount to scroll. Note: this is always positive! Direction
2849 * needs to be taken into account when actually scrolling.
2850 */
2851 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2852 int amountToScroll = 0;
2853 newFocus.getDrawingRect(mTempRect);
2854 offsetDescendantRectToMyCoords(newFocus, mTempRect);
2855 if (direction == View.FOCUS_UP) {
2856 if (mTempRect.top < mListPadding.top) {
2857 amountToScroll = mListPadding.top - mTempRect.top;
2858 if (positionOfNewFocus > 0) {
2859 amountToScroll += getArrowScrollPreviewLength();
2860 }
2861 }
2862 } else {
2863 final int listBottom = getHeight() - mListPadding.bottom;
2864 if (mTempRect.bottom > listBottom) {
2865 amountToScroll = mTempRect.bottom - listBottom;
2866 if (positionOfNewFocus < mItemCount - 1) {
2867 amountToScroll += getArrowScrollPreviewLength();
2868 }
2869 }
2870 }
2871 return amountToScroll;
2872 }
2873
2874 /**
2875 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08002876 * direction.
2877 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002878 * @param descendant A descendant of this list.
2879 * @return The distance, or 0 if the nearest edge is already on screen.
2880 */
2881 private int distanceToView(View descendant) {
2882 int distance = 0;
2883 descendant.getDrawingRect(mTempRect);
2884 offsetDescendantRectToMyCoords(descendant, mTempRect);
2885 final int listBottom = mBottom - mTop - mListPadding.bottom;
2886 if (mTempRect.bottom < mListPadding.top) {
2887 distance = mListPadding.top - mTempRect.bottom;
2888 } else if (mTempRect.top > listBottom) {
2889 distance = mTempRect.top - listBottom;
2890 }
2891 return distance;
2892 }
2893
2894
2895 /**
2896 * Scroll the children by amount, adding a view at the end and removing
2897 * views that fall off as necessary.
2898 *
2899 * @param amount The amount (positive or negative) to scroll.
2900 */
2901 private void scrollListItemsBy(int amount) {
2902 offsetChildrenTopAndBottom(amount);
2903
2904 final int listBottom = getHeight() - mListPadding.bottom;
2905 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002906 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002907
2908 if (amount < 0) {
2909 // shifted items up
2910
2911 // may need to pan views into the bottom space
2912 int numChildren = getChildCount();
2913 View last = getChildAt(numChildren - 1);
2914 while (last.getBottom() < listBottom) {
2915 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
2916 if (lastVisiblePosition < mItemCount - 1) {
2917 last = addViewBelow(last, lastVisiblePosition);
2918 numChildren++;
2919 } else {
2920 break;
2921 }
2922 }
2923
2924 // may have brought in the last child of the list that is skinnier
2925 // than the fading edge, thereby leaving space at the end. need
2926 // to shift back
2927 if (last.getBottom() < listBottom) {
2928 offsetChildrenTopAndBottom(listBottom - last.getBottom());
2929 }
2930
2931 // top views may be panned off screen
2932 View first = getChildAt(0);
2933 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002934 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
2935 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Romain Guy2d51bff2010-01-19 17:34:10 -08002936 detachViewFromParent(first);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002937 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002938 } else {
Romain Guy2d51bff2010-01-19 17:34:10 -08002939 removeViewInLayout(first);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002940 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002941 first = getChildAt(0);
2942 mFirstPosition++;
2943 }
2944 } else {
2945 // shifted items down
2946 View first = getChildAt(0);
2947
2948 // may need to pan views into top
2949 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
2950 first = addViewAbove(first, mFirstPosition);
2951 mFirstPosition--;
2952 }
2953
2954 // may have brought the very first child of the list in too far and
2955 // need to shift it back
2956 if (first.getTop() > listTop) {
2957 offsetChildrenTopAndBottom(listTop - first.getTop());
2958 }
2959
2960 int lastIndex = getChildCount() - 1;
2961 View last = getChildAt(lastIndex);
2962
2963 // bottom view may be panned off screen
2964 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002965 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
2966 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Romain Guy2d51bff2010-01-19 17:34:10 -08002967 detachViewFromParent(last);
Dianne Hackborn079e2352010-10-18 17:02:43 -07002968 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002969 } else {
Romain Guy2d51bff2010-01-19 17:34:10 -08002970 removeViewInLayout(last);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07002971 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002972 last = getChildAt(--lastIndex);
2973 }
2974 }
2975 }
2976
2977 private View addViewAbove(View theView, int position) {
2978 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08002979 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002980 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08002981 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
2982 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002983 return view;
2984 }
2985
2986 private View addViewBelow(View theView, int position) {
2987 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08002988 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002989 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08002990 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
2991 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002992 return view;
2993 }
2994
2995 /**
2996 * Indicates that the views created by the ListAdapter can contain focusable
2997 * items.
2998 *
2999 * @param itemsCanFocus true if items can get focus, false otherwise
3000 */
3001 public void setItemsCanFocus(boolean itemsCanFocus) {
3002 mItemsCanFocus = itemsCanFocus;
3003 if (!itemsCanFocus) {
3004 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3005 }
3006 }
3007
3008 /**
3009 * @return Whether the views created by the ListAdapter can contain focusable
3010 * items.
3011 */
3012 public boolean getItemsCanFocus() {
3013 return mItemsCanFocus;
3014 }
3015
3016 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003017 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003018 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f52009-05-15 16:03:59 -07003019 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003020 if (retValue) {
3021 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003022 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003023 View first = getChildAt(0);
3024 if (first == null || first.getTop() > listTop) {
3025 return false;
3026 }
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003027 final int listBottom = getHeight() -
3028 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003029 View last = getChildAt(getChildCount() - 1);
3030 if (last == null || last.getBottom() < listBottom) {
3031 return false;
3032 }
3033 }
3034 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003035 }
3036
3037 @Override
3038 public void setCacheColorHint(int color) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003039 final boolean opaque = (color >>> 24) == 0xFF;
3040 mIsCacheColorOpaque = opaque;
3041 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003042 if (mDividerPaint == null) {
3043 mDividerPaint = new Paint();
3044 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003045 mDividerPaint.setColor(color);
3046 }
Romain Guy24443ea2009-05-11 11:56:30 -07003047 super.setCacheColorHint(color);
3048 }
Adam Powell637d3372010-08-25 14:37:03 -07003049
3050 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3051 final int height = drawable.getMinimumHeight();
3052
3053 canvas.save();
3054 canvas.clipRect(bounds);
3055
3056 final int span = bounds.bottom - bounds.top;
3057 if (span < height) {
3058 bounds.top = bounds.bottom - height;
3059 }
3060
3061 drawable.setBounds(bounds);
3062 drawable.draw(canvas);
3063
3064 canvas.restore();
3065 }
3066
3067 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3068 final int height = drawable.getMinimumHeight();
3069
3070 canvas.save();
3071 canvas.clipRect(bounds);
3072
3073 final int span = bounds.bottom - bounds.top;
3074 if (span < height) {
3075 bounds.bottom = bounds.top + height;
3076 }
3077
3078 drawable.setBounds(bounds);
3079 drawable.draw(canvas);
3080
3081 canvas.restore();
3082 }
3083
Romain Guy24443ea2009-05-11 11:56:30 -07003084 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003085 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003086 if (mCachingStarted) {
3087 mCachingActive = true;
3088 }
3089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003090 // Draw the dividers
3091 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003092 final Drawable overscrollHeader = mOverScrollHeader;
3093 final Drawable overscrollFooter = mOverScrollFooter;
3094 final boolean drawOverscrollHeader = overscrollHeader != null;
3095 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003096 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003097
Adam Powell637d3372010-08-25 14:37:03 -07003098 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003099 // Only modify the top and bottom in the loop, we set the left and right here
3100 final Rect bounds = mTempRect;
3101 bounds.left = mPaddingLeft;
3102 bounds.right = mRight - mLeft - mPaddingRight;
3103
3104 final int count = getChildCount();
3105 final int headerCount = mHeaderViewInfos.size();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003106 final int itemCount = mItemCount;
3107 final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003108 final boolean headerDividers = mHeaderDividersEnabled;
3109 final boolean footerDividers = mFooterDividersEnabled;
3110 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003111 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3112 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003113 // If the list is opaque *and* the background is not, we want to
3114 // fill a rect where the dividers would be for non-selectable items
3115 // If the list is opaque and the background is also opaque, we don't
3116 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003117 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003118
3119 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003120 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003121 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003122 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003123 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003124
Adam Powell94566552011-01-05 23:25:33 -08003125 int effectivePaddingTop = 0;
3126 int effectivePaddingBottom = 0;
3127 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3128 effectivePaddingTop = mListPadding.top;
3129 effectivePaddingBottom = mListPadding.bottom;
3130 }
3131
3132 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003133 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003134 int bottom = 0;
Adam Powell0b8bb422010-02-08 14:30:45 -08003135
Adam Powell637d3372010-08-25 14:37:03 -07003136 // Draw top divider or header for overscroll
3137 final int scrollY = mScrollY;
3138 if (count > 0 && scrollY < 0) {
3139 if (drawOverscrollHeader) {
3140 bounds.bottom = 0;
3141 bounds.top = scrollY;
3142 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3143 } else if (drawDividers) {
3144 bounds.bottom = 0;
3145 bounds.top = -dividerHeight;
3146 drawDivider(canvas, bounds, -1);
3147 }
3148 }
3149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003150 for (int i = 0; i < count; i++) {
3151 if ((headerDividers || first + i >= headerCount) &&
3152 (footerDividers || first + i < footerLimit)) {
3153 View child = getChildAt(i);
3154 bottom = child.getBottom();
Romain Guy2bed2272009-03-24 18:23:21 -07003155 // Don't draw dividers next to items that are not enabled
Adam Powell637d3372010-08-25 14:37:03 -07003156
3157 if (drawDividers &&
3158 (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
3159 if ((areAllItemsSelectable ||
3160 (adapter.isEnabled(first + i) && (i == count - 1 ||
3161 adapter.isEnabled(first + i + 1))))) {
3162 bounds.top = bottom;
3163 bounds.bottom = bottom + dividerHeight;
3164 drawDivider(canvas, bounds, i);
3165 } else if (fillForMissingDividers) {
3166 bounds.top = bottom;
3167 bounds.bottom = bottom + dividerHeight;
3168 canvas.drawRect(bounds, paint);
3169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003170 }
3171 }
3172 }
Adam Powell637d3372010-08-25 14:37:03 -07003173
3174 final int overFooterBottom = mBottom + mScrollY;
3175 if (drawOverscrollFooter && first + count == itemCount &&
3176 overFooterBottom > bottom) {
3177 bounds.top = bottom;
3178 bounds.bottom = overFooterBottom;
3179 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3180 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003181 } else {
3182 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003183
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003184 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003185
Adam Powell637d3372010-08-25 14:37:03 -07003186 if (count > 0 && drawOverscrollHeader) {
3187 bounds.top = scrollY;
3188 bounds.bottom = getChildAt(0).getTop();
3189 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3190 }
3191
3192 final int start = drawOverscrollHeader ? 1 : 0;
3193 for (int i = start; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003194 if ((headerDividers || first + i >= headerCount) &&
3195 (footerDividers || first + i < footerLimit)) {
3196 View child = getChildAt(i);
3197 top = child.getTop();
Romain Guy2bed2272009-03-24 18:23:21 -07003198 // Don't draw dividers next to items that are not enabled
Romain Guy0211a0a2011-02-14 16:34:59 -08003199 if (top > effectivePaddingTop) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003200 if ((areAllItemsSelectable ||
3201 (adapter.isEnabled(first + i) && (i == count - 1 ||
3202 adapter.isEnabled(first + i + 1))))) {
3203 bounds.top = top - dividerHeight;
3204 bounds.bottom = top;
3205 // Give the method the child ABOVE the divider, so we
3206 // subtract one from our child
3207 // position. Give -1 when there is no child above the
3208 // divider.
3209 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003210 } else if (fillForMissingDividers) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003211 bounds.top = top - dividerHeight;
3212 bounds.bottom = top;
3213 canvas.drawRect(bounds, paint);
3214 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003215 }
3216 }
3217 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003218
Romain Guy179de8a2010-07-09 13:27:00 -07003219 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003220 if (drawOverscrollFooter) {
3221 final int absListBottom = mBottom;
3222 bounds.top = absListBottom;
3223 bounds.bottom = absListBottom + scrollY;
3224 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3225 } else if (drawDividers) {
3226 bounds.top = listBottom;
3227 bounds.bottom = listBottom + dividerHeight;
3228 drawDivider(canvas, bounds, -1);
3229 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003231 }
3232 }
3233
3234 // Draw the indicators (these should be drawn above the dividers) and children
3235 super.dispatchDraw(canvas);
3236 }
3237
Romain Guy0211a0a2011-02-14 16:34:59 -08003238 @Override
3239 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3240 boolean more = super.drawChild(canvas, child, drawingTime);
3241 if (mCachingActive && child.mCachingFailed) {
3242 mCachingActive = false;
3243 }
3244 return more;
3245 }
3246
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003247 /**
3248 * Draws a divider for the given child in the given bounds.
3249 *
3250 * @param canvas The canvas to draw to.
3251 * @param bounds The bounds of the divider.
3252 * @param childIndex The index of child (of the View) above the divider.
3253 * This will be -1 if there is no child above the divider to be
3254 * drawn.
3255 */
3256 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3257 // This widget draws the same divider for all children
3258 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003259
Romain Guy95930e12010-10-04 13:46:02 -07003260 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003261 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003262 }
3263
3264 /**
3265 * Returns the drawable that will be drawn between each item in the list.
3266 *
3267 * @return the current drawable drawn between list elements
3268 */
3269 public Drawable getDivider() {
3270 return mDivider;
3271 }
3272
3273 /**
3274 * Sets the drawable that will be drawn between each item in the list. If the drawable does
3275 * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3276 *
3277 * @param divider The drawable to use.
3278 */
3279 public void setDivider(Drawable divider) {
3280 if (divider != null) {
3281 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003282 } else {
3283 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003284 }
3285 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003286 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003287 requestLayout();
3288 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003289 }
3290
3291 /**
3292 * @return Returns the height of the divider that will be drawn between each item in the list.
3293 */
3294 public int getDividerHeight() {
3295 return mDividerHeight;
3296 }
3297
3298 /**
3299 * Sets the height of the divider that will be drawn between each item in the list. Calling
3300 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3301 *
3302 * @param height The new height of the divider in pixels.
3303 */
3304 public void setDividerHeight(int height) {
3305 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003306 requestLayout();
3307 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003308 }
3309
3310 /**
3311 * Enables or disables the drawing of the divider for header views.
3312 *
3313 * @param headerDividersEnabled True to draw the headers, false otherwise.
3314 *
3315 * @see #setFooterDividersEnabled(boolean)
3316 * @see #addHeaderView(android.view.View)
3317 */
3318 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3319 mHeaderDividersEnabled = headerDividersEnabled;
3320 invalidate();
3321 }
3322
3323 /**
3324 * Enables or disables the drawing of the divider for footer views.
3325 *
3326 * @param footerDividersEnabled True to draw the footers, false otherwise.
3327 *
3328 * @see #setHeaderDividersEnabled(boolean)
3329 * @see #addFooterView(android.view.View)
3330 */
3331 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3332 mFooterDividersEnabled = footerDividersEnabled;
3333 invalidate();
3334 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003335
Adam Powell637d3372010-08-25 14:37:03 -07003336 /**
3337 * Sets the drawable that will be drawn above all other list content.
3338 * This area can become visible when the user overscrolls the list.
3339 *
3340 * @param header The drawable to use
3341 */
3342 public void setOverscrollHeader(Drawable header) {
3343 mOverScrollHeader = header;
3344 if (mScrollY < 0) {
3345 invalidate();
3346 }
3347 }
3348
3349 /**
3350 * @return The drawable that will be drawn above all other list content
3351 */
3352 public Drawable getOverscrollHeader() {
3353 return mOverScrollHeader;
3354 }
3355
3356 /**
3357 * Sets the drawable that will be drawn below all other list content.
3358 * This area can become visible when the user overscrolls the list,
3359 * or when the list's content does not fully fill the container area.
3360 *
3361 * @param footer The drawable to use
3362 */
3363 public void setOverscrollFooter(Drawable footer) {
3364 mOverScrollFooter = footer;
3365 invalidate();
3366 }
3367
3368 /**
3369 * @return The drawable that will be drawn below all other list content
3370 */
3371 public Drawable getOverscrollFooter() {
3372 return mOverScrollFooter;
3373 }
3374
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003375 @Override
3376 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3377 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3378
3379 int closetChildIndex = -1;
3380 if (gainFocus && previouslyFocusedRect != null) {
3381 previouslyFocusedRect.offset(mScrollX, mScrollY);
3382
Adam Powellc854f282009-12-16 14:11:53 -08003383 final ListAdapter adapter = mAdapter;
Adam Powelld7507832010-02-18 15:40:33 -08003384 // Don't cache the result of getChildCount or mFirstPosition here,
3385 // it could change in layoutChildren.
3386 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003387 mLayoutMode = LAYOUT_NORMAL;
3388 layoutChildren();
3389 }
3390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003391 // figure out which item should be selected based on previously
3392 // focused rect
3393 Rect otherRect = mTempRect;
3394 int minDistance = Integer.MAX_VALUE;
3395 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003396 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003397
3398 for (int i = 0; i < childCount; i++) {
3399 // only consider selectable views
3400 if (!adapter.isEnabled(firstPosition + i)) {
3401 continue;
3402 }
3403
3404 View other = getChildAt(i);
3405 other.getDrawingRect(otherRect);
3406 offsetDescendantRectToMyCoords(other, otherRect);
3407 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3408
3409 if (distance < minDistance) {
3410 minDistance = distance;
3411 closetChildIndex = i;
3412 }
3413 }
3414 }
3415
3416 if (closetChildIndex >= 0) {
3417 setSelection(closetChildIndex + mFirstPosition);
3418 } else {
3419 requestLayout();
3420 }
3421 }
3422
3423
3424 /*
3425 * (non-Javadoc)
3426 *
3427 * Children specified in XML are assumed to be header views. After we have
3428 * parsed them move them out of the children list and into mHeaderViews.
3429 */
3430 @Override
3431 protected void onFinishInflate() {
3432 super.onFinishInflate();
3433
3434 int count = getChildCount();
3435 if (count > 0) {
3436 for (int i = 0; i < count; ++i) {
3437 addHeaderView(getChildAt(i));
3438 }
3439 removeAllViews();
3440 }
3441 }
3442
3443 /* (non-Javadoc)
3444 * @see android.view.View#findViewById(int)
3445 * First look in our children, then in any header and footer views that may be scrolled off.
3446 */
3447 @Override
3448 protected View findViewTraversal(int id) {
3449 View v;
3450 v = super.findViewTraversal(id);
3451 if (v == null) {
3452 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3453 if (v != null) {
3454 return v;
3455 }
3456 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3457 if (v != null) {
3458 return v;
3459 }
3460 }
3461 return v;
3462 }
3463
3464 /* (non-Javadoc)
3465 *
3466 * Look in the passed in list of headers or footers for the view.
3467 */
3468 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3469 if (where != null) {
3470 int len = where.size();
3471 View v;
3472
3473 for (int i = 0; i < len; i++) {
3474 v = where.get(i).view;
3475
3476 if (!v.isRootNamespace()) {
3477 v = v.findViewById(id);
3478
3479 if (v != null) {
3480 return v;
3481 }
3482 }
3483 }
3484 }
3485 return null;
3486 }
3487
3488 /* (non-Javadoc)
Jeff Brown4e6319b2010-12-13 10:36:51 -08003489 * @see android.view.View#findViewWithTag(Object)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003490 * First look in our children, then in any header and footer views that may be scrolled off.
3491 */
3492 @Override
3493 protected View findViewWithTagTraversal(Object tag) {
3494 View v;
3495 v = super.findViewWithTagTraversal(tag);
3496 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003497 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003498 if (v != null) {
3499 return v;
3500 }
3501
Jeff Brown4e6319b2010-12-13 10:36:51 -08003502 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003503 if (v != null) {
3504 return v;
3505 }
3506 }
3507 return v;
3508 }
3509
3510 /* (non-Javadoc)
3511 *
3512 * Look in the passed in list of headers or footers for the view with the tag.
3513 */
Jeff Brown4e6319b2010-12-13 10:36:51 -08003514 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003515 if (where != null) {
3516 int len = where.size();
3517 View v;
3518
3519 for (int i = 0; i < len; i++) {
3520 v = where.get(i).view;
3521
3522 if (!v.isRootNamespace()) {
3523 v = v.findViewWithTag(tag);
3524
3525 if (v != null) {
3526 return v;
3527 }
3528 }
3529 }
3530 }
3531 return null;
3532 }
3533
Jeff Brown4e6319b2010-12-13 10:36:51 -08003534 /**
3535 * @hide
3536 * @see android.view.View#findViewByPredicate(Predicate)
3537 * First look in our children, then in any header and footer views that may be scrolled off.
3538 */
3539 @Override
3540 protected View findViewByPredicateTraversal(Predicate<View> predicate) {
3541 View v;
3542 v = super.findViewByPredicateTraversal(predicate);
3543 if (v == null) {
3544 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate);
3545 if (v != null) {
3546 return v;
3547 }
3548
3549 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate);
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 first view that matches
3560 * the predicate.
3561 */
3562 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
3563 Predicate<View> predicate) {
3564 if (where != null) {
3565 int len = where.size();
3566 View v;
3567
3568 for (int i = 0; i < len; i++) {
3569 v = where.get(i).view;
3570
3571 if (!v.isRootNamespace()) {
3572 v = v.findViewByPredicate(predicate);
3573
3574 if (v != null) {
3575 return v;
3576 }
3577 }
3578 }
3579 }
3580 return null;
3581 }
3582
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003583 @Override
3584 public boolean onTouchEvent(MotionEvent ev) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003585 //noinspection SimplifiableIfStatement
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003586 if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
3587 // Don't handle edge touches immediately -- they may actually belong to one of our
3588 // descendants.
3589 return false;
3590 }
3591 return super.onTouchEvent(ev);
3592 }
3593
3594 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003595 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003596 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3597 *
3598 * @return A new array which contains the id of each checked item in the
3599 * list.
3600 *
Adam Powell463ceff2010-03-09 11:50:51 -08003601 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003602 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003603 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003604 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003605 // Use new behavior that correctly handles stable ID mapping.
3606 if (mAdapter != null && mAdapter.hasStableIds()) {
3607 return getCheckedItemIds();
3608 }
3609
3610 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3611 // Fall back to it to support legacy apps.
3612 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3613 final SparseBooleanArray states = mCheckStates;
3614 final int count = states.size();
3615 final long[] ids = new long[count];
3616 final ListAdapter adapter = mAdapter;
3617
3618 int checkedCount = 0;
3619 for (int i = 0; i < count; i++) {
3620 if (states.valueAt(i)) {
3621 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3622 }
3623 }
3624
3625 // Trim array if needed. mCheckStates may contain false values
3626 // resulting in checkedCount being smaller than count.
3627 if (checkedCount == count) {
3628 return ids;
3629 } else {
3630 final long[] result = new long[checkedCount];
3631 System.arraycopy(ids, 0, result, 0, checkedCount);
3632
3633 return result;
3634 }
3635 }
3636 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08003637 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003638}