blob: eeb80157fe3d8537a242039f9a6f699152a620f1 [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
Romain Guy5fade8c2013-07-10 16:36:18 -070019import android.os.Trace;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080020import com.android.internal.R;
Jeff Brown4e6319b2010-12-13 10:36:51 -080021import com.android.internal.util.Predicate;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080022import com.google.android.collect.Lists;
23
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070025import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.res.TypedArray;
27import android.graphics.Canvas;
Romain Guy8f1344f2009-05-15 16:03:59 -070028import android.graphics.Paint;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080029import android.graphics.PixelFormat;
30import android.graphics.Rect;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080031import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.util.AttributeSet;
alanv30ee76c2012-09-07 10:31:16 -070033import android.util.MathUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.SparseBooleanArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.view.FocusFinder;
36import android.view.KeyEvent;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080037import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.view.View;
39import android.view.ViewDebug;
40import android.view.ViewGroup;
41import android.view.ViewParent;
Alan Viverette3e141622014-02-18 17:05:13 -080042import android.view.ViewRootImpl;
svetoslavganov75986cf2009-05-14 22:28:01 -070043import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080044import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette5b2081d2013-08-28 10:43:07 -070045import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
46import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
Alan Viverette3e141622014-02-18 17:05:13 -080047import android.view.accessibility.AccessibilityNodeProvider;
Winson Chung499cb9f2010-07-16 11:18:17 -070048import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import java.util.ArrayList;
51
52/*
53 * Implementation Notes:
54 *
55 * Some terminology:
56 *
57 * index - index of the items that are currently visible
58 * position - index of the items in the cursor
59 */
60
61
62/**
63 * A view that shows items in a vertically scrolling list. The items
64 * come from the {@link ListAdapter} associated with this view.
65 *
Scott Main4c359b72012-07-24 15:51:27 -070066 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
67 * guide.</p>
Scott Main41ec6532010-08-19 16:57:07 -070068 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 * @attr ref android.R.styleable#ListView_entries
70 * @attr ref android.R.styleable#ListView_divider
71 * @attr ref android.R.styleable#ListView_dividerHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 * @attr ref android.R.styleable#ListView_headerDividersEnabled
73 * @attr ref android.R.styleable#ListView_footerDividersEnabled
74 */
Winson Chung499cb9f2010-07-16 11:18:17 -070075@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076public class ListView extends AbsListView {
77 /**
78 * Used to indicate a no preference for a position type.
79 */
80 static final int NO_POSITION = -1;
81
82 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 * When arrow scrolling, ListView will never scroll more than this factor
84 * times the height of the list.
85 */
86 private static final float MAX_SCROLL_FACTOR = 0.33f;
87
88 /**
89 * When arrow scrolling, need a certain amount of pixels to preview next
90 * items. This is usually the fading edge, but if that is small enough,
91 * we want to make sure we preview at least this many pixels.
92 */
93 private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
94
95 /**
96 * A class that represents a fixed view in a list, for example a header at the top
97 * or a footer at the bottom.
98 */
99 public class FixedViewInfo {
100 /** The view to add to the list */
101 public View view;
102 /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
103 public Object data;
104 /** <code>true</code> if the fixed view should be selectable in the list */
105 public boolean isSelectable;
106 }
107
108 private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
109 private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
110
111 Drawable mDivider;
112 int mDividerHeight;
Adam Cohenfb603862010-12-17 12:03:17 -0800113
Adam Powell637d3372010-08-25 14:37:03 -0700114 Drawable mOverScrollHeader;
115 Drawable mOverScrollFooter;
116
Romain Guy24443ea2009-05-11 11:56:30 -0700117 private boolean mIsCacheColorOpaque;
118 private boolean mDividerIsOpaque;
Romain Guy24443ea2009-05-11 11:56:30 -0700119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 private boolean mHeaderDividersEnabled;
121 private boolean mFooterDividersEnabled;
122
123 private boolean mAreAllItemsSelectable = true;
124
125 private boolean mItemsCanFocus = false;
126
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 // used for temporary calculations.
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700128 private final Rect mTempRect = new Rect();
Romain Guya02903f2009-05-23 13:26:46 -0700129 private Paint mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130
131 // the single allocated result per list view; kinda cheesey but avoids
132 // allocating these thingies too often.
Romain Guy9c3184cc2010-02-25 17:32:54 -0800133 private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134
Adam Powell9bf3c122010-02-26 11:32:07 -0800135 // Keeps focused children visible through resizes
136 private FocusSelector mFocusSelector;
Adam Powell8350f7d2010-07-28 14:27:28 -0700137
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 public ListView(Context context) {
139 this(context, null);
140 }
141
142 public ListView(Context context, AttributeSet attrs) {
143 this(context, attrs, com.android.internal.R.attr.listViewStyle);
144 }
145
Alan Viverette617feb92013-09-09 18:09:13 -0700146 public ListView(Context context, AttributeSet attrs, int defStyleAttr) {
147 this(context, attrs, defStyleAttr, 0);
148 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149
Alan Viverette617feb92013-09-09 18:09:13 -0700150 public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
151 super(context, attrs, defStyleAttr, defStyleRes);
152
153 final TypedArray a = context.obtainStyledAttributes(
154 attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155
156 CharSequence[] entries = a.getTextArray(
157 com.android.internal.R.styleable.ListView_entries);
158 if (entries != null) {
159 setAdapter(new ArrayAdapter<CharSequence>(context,
160 com.android.internal.R.layout.simple_list_item_1, entries));
161 }
162
163 final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
164 if (d != null) {
165 // If a divider is specified use its intrinsic height for divider height
166 setDivider(d);
167 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -0800168
Adam Powell637d3372010-08-25 14:37:03 -0700169 final Drawable osHeader = a.getDrawable(
170 com.android.internal.R.styleable.ListView_overScrollHeader);
171 if (osHeader != null) {
172 setOverscrollHeader(osHeader);
173 }
174
175 final Drawable osFooter = a.getDrawable(
176 com.android.internal.R.styleable.ListView_overScrollFooter);
177 if (osFooter != null) {
178 setOverscrollFooter(osFooter);
179 }
180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 // Use the height specified, zero being the default
182 final int dividerHeight = a.getDimensionPixelSize(
183 com.android.internal.R.styleable.ListView_dividerHeight, 0);
184 if (dividerHeight != 0) {
185 setDividerHeight(dividerHeight);
186 }
187
188 mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
189 mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
190
191 a.recycle();
192 }
193
194 /**
195 * @return The maximum amount a list view will scroll in response to
196 * an arrow event.
197 */
198 public int getMaxScrollAmount() {
199 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
200 }
201
202 /**
203 * Make sure views are touching the top or bottom edge, as appropriate for
204 * our gravity
205 */
206 private void adjustViewsUpOrDown() {
207 final int childCount = getChildCount();
208 int delta;
209
210 if (childCount > 0) {
211 View child;
212
213 if (!mStackFromBottom) {
214 // Uh-oh -- we came up short. Slide all views up to make them
215 // align with the top
216 child = getChildAt(0);
217 delta = child.getTop() - mListPadding.top;
218 if (mFirstPosition != 0) {
219 // It's OK to have some space above the first item if it is
220 // part of the vertical spacing
221 delta -= mDividerHeight;
222 }
223 if (delta < 0) {
224 // We only are looking to see if we are too low, not too high
225 delta = 0;
226 }
227 } else {
228 // we are too high, slide all views down to align with bottom
229 child = getChildAt(childCount - 1);
230 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
231
232 if (mFirstPosition + childCount < mItemCount) {
233 // It's OK to have some space below the last item if it is
234 // part of the vertical spacing
235 delta += mDividerHeight;
236 }
237
238 if (delta > 0) {
239 delta = 0;
240 }
241 }
242
243 if (delta != 0) {
244 offsetChildrenTopAndBottom(-delta);
245 }
246 }
247 }
248
249 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700250 * Add a fixed view to appear at the top of the list. If this method is
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 * called more than once, the views will appear in the order they were
252 * added. Views added using this call can take focus if they want.
253 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700254 * Note: When first introduced, this method could only be called before
255 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700256 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700257 * called at any time. If the ListView's adapter does not extend
258 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
259 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 *
261 * @param v The view to add.
262 * @param data Data to associate with this view
263 * @param isSelectable whether the item is selectable
264 */
265 public void addHeaderView(View v, Object data, boolean isSelectable) {
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700266 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 info.view = v;
268 info.data = data;
269 info.isSelectable = isSelectable;
270 mHeaderViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800271 mAreAllItemsSelectable &= isSelectable;
Marco Nelissen22c04a32011-04-19 14:07:55 -0700272
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700273 // Wrap the adapter if it wasn't already wrapped.
274 if (mAdapter != null) {
275 if (!(mAdapter instanceof HeaderViewListAdapter)) {
276 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
277 }
278
279 // In the case of re-adding a header view, or adding one later on,
280 // we need to notify the observer.
281 if (mDataSetObserver != null) {
282 mDataSetObserver.onChanged();
283 }
Marco Nelissen22c04a32011-04-19 14:07:55 -0700284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 }
286
287 /**
288 * Add a fixed view to appear at the top of the list. If addHeaderView is
289 * called more than once, the views will appear in the order they were
290 * added. Views added using this call can take focus if they want.
291 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700292 * Note: When first introduced, this method could only be called before
293 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700294 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700295 * called at any time. If the ListView's adapter does not extend
296 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
297 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 *
299 * @param v The view to add.
300 */
301 public void addHeaderView(View v) {
302 addHeaderView(v, null, true);
303 }
304
305 @Override
306 public int getHeaderViewsCount() {
307 return mHeaderViewInfos.size();
308 }
309
310 /**
311 * Removes a previously-added header view.
312 *
313 * @param v The view to remove
314 * @return true if the view was removed, false if the view was not a header
315 * view
316 */
317 public boolean removeHeaderView(View v) {
318 if (mHeaderViewInfos.size() > 0) {
319 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700320 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700321 if (mDataSetObserver != null) {
322 mDataSetObserver.onChanged();
323 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 result = true;
325 }
326 removeFixedViewInfo(v, mHeaderViewInfos);
327 return result;
328 }
329 return false;
330 }
331
332 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
333 int len = where.size();
334 for (int i = 0; i < len; ++i) {
335 FixedViewInfo info = where.get(i);
336 if (info.view == v) {
337 where.remove(i);
338 break;
339 }
340 }
341 }
342
343 /**
344 * Add a fixed view to appear at the bottom of the list. If addFooterView is
345 * called more than once, the views will appear in the order they were
346 * added. Views added using this call can take focus if they want.
347 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700348 * Note: When first introduced, this method could only be called before
349 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700350 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700351 * called at any time. If the ListView's adapter does not extend
352 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
353 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 *
355 * @param v The view to add.
356 * @param data Data to associate with this view
357 * @param isSelectable true if the footer view can be selected
358 */
359 public void addFooterView(View v, Object data, boolean isSelectable) {
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700360 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 info.view = v;
362 info.data = data;
363 info.isSelectable = isSelectable;
364 mFooterViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800365 mAreAllItemsSelectable &= isSelectable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700367 // Wrap the adapter if it wasn't already wrapped.
368 if (mAdapter != null) {
369 if (!(mAdapter instanceof HeaderViewListAdapter)) {
370 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
371 }
372
373 // In the case of re-adding a footer view, or adding one later on,
374 // we need to notify the observer.
375 if (mDataSetObserver != null) {
376 mDataSetObserver.onChanged();
377 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 }
379 }
380
381 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700382 * Add a fixed view to appear at the bottom of the list. If addFooterView is
383 * called more than once, the views will appear in the order they were
384 * added. Views added using this call can take focus if they want.
385 * <p>
386 * Note: When first introduced, this method could only be called before
387 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700388 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700389 * called at any time. If the ListView's adapter does not extend
390 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
391 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 *
393 * @param v The view to add.
394 */
395 public void addFooterView(View v) {
396 addFooterView(v, null, true);
397 }
398
399 @Override
400 public int getFooterViewsCount() {
401 return mFooterViewInfos.size();
402 }
403
404 /**
405 * Removes a previously-added footer view.
406 *
407 * @param v The view to remove
408 * @return
409 * true if the view was removed, false if the view was not a footer view
410 */
411 public boolean removeFooterView(View v) {
412 if (mFooterViewInfos.size() > 0) {
413 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700414 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700415 if (mDataSetObserver != null) {
416 mDataSetObserver.onChanged();
417 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 result = true;
419 }
420 removeFixedViewInfo(v, mFooterViewInfos);
421 return result;
422 }
423 return false;
424 }
425
426 /**
427 * Returns the adapter currently in use in this ListView. The returned adapter
428 * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
429 * might be a {@link WrapperListAdapter}.
430 *
431 * @return The adapter currently used to display data in this ListView.
432 *
433 * @see #setAdapter(ListAdapter)
434 */
435 @Override
436 public ListAdapter getAdapter() {
437 return mAdapter;
438 }
439
440 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700441 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
442 * through the specified intent.
443 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
444 */
445 @android.view.RemotableViewMethod
446 public void setRemoteViewsAdapter(Intent intent) {
447 super.setRemoteViewsAdapter(intent);
448 }
449
450 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 * Sets the data behind this ListView.
452 *
453 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
454 * depending on the ListView features currently in use. For instance, adding
455 * headers and/or footers will cause the adapter to be wrapped.
456 *
457 * @param adapter The ListAdapter which is responsible for maintaining the
458 * data backing this list and for producing a view to represent an
459 * item in that data set.
460 *
461 * @see #getAdapter()
462 */
463 @Override
464 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700465 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 mAdapter.unregisterDataSetObserver(mDataSetObserver);
467 }
468
469 resetList();
470 mRecycler.clear();
471
472 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
473 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
474 } else {
475 mAdapter = adapter;
476 }
477
478 mOldSelectedPosition = INVALID_POSITION;
479 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700480
481 // AbsListView#setAdapter will update choice mode states.
482 super.setAdapter(adapter);
483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 if (mAdapter != null) {
485 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
486 mOldItemCount = mItemCount;
487 mItemCount = mAdapter.getCount();
488 checkFocus();
489
490 mDataSetObserver = new AdapterDataSetObserver();
491 mAdapter.registerDataSetObserver(mDataSetObserver);
492
493 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
494
495 int position;
496 if (mStackFromBottom) {
497 position = lookForSelectablePosition(mItemCount - 1, false);
498 } else {
499 position = lookForSelectablePosition(0, true);
500 }
501 setSelectedPositionInt(position);
502 setNextSelectedPositionInt(position);
503
504 if (mItemCount == 0) {
505 // Nothing selected
506 checkSelectionChanged();
507 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 } else {
509 mAreAllItemsSelectable = true;
510 checkFocus();
511 // Nothing selected
512 checkSelectionChanged();
513 }
514
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 requestLayout();
516 }
517
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800518 /**
519 * The list is empty. Clear everything out.
520 */
521 @Override
522 void resetList() {
Romain Guy2e447d42009-04-28 18:01:24 -0700523 // The parent's resetList() will remove all views from the layout so we need to
524 // cleanup the state of our footers and headers
525 clearRecycledState(mHeaderViewInfos);
526 clearRecycledState(mFooterViewInfos);
527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 super.resetList();
Romain Guy2e447d42009-04-28 18:01:24 -0700529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 mLayoutMode = LAYOUT_NORMAL;
531 }
532
Romain Guy2e447d42009-04-28 18:01:24 -0700533 private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
534 if (infos != null) {
535 final int count = infos.size();
536
537 for (int i = 0; i < count; i++) {
538 final View child = infos.get(i).view;
539 final LayoutParams p = (LayoutParams) child.getLayoutParams();
540 if (p != null) {
541 p.recycledHeaderFooter = false;
542 }
543 }
544 }
545 }
546
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800547 /**
548 * @return Whether the list needs to show the top fading edge
549 */
550 private boolean showingTopFadingEdge() {
551 final int listTop = mScrollY + mListPadding.top;
552 return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
553 }
554
555 /**
556 * @return Whether the list needs to show the bottom fading edge
557 */
558 private boolean showingBottomFadingEdge() {
559 final int childCount = getChildCount();
560 final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
561 final int lastVisiblePosition = mFirstPosition + childCount - 1;
562
563 final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
564
565 return (lastVisiblePosition < mItemCount - 1)
566 || (bottomOfBottomChild < listBottom);
567 }
568
569
570 @Override
571 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
572
573 int rectTopWithinChild = rect.top;
574
575 // offset so rect is in coordinates of the this view
576 rect.offset(child.getLeft(), child.getTop());
577 rect.offset(-child.getScrollX(), -child.getScrollY());
578
579 final int height = getHeight();
580 int listUnfadedTop = getScrollY();
581 int listUnfadedBottom = listUnfadedTop + height;
582 final int fadingEdge = getVerticalFadingEdgeLength();
583
584 if (showingTopFadingEdge()) {
585 // leave room for top fading edge as long as rect isn't at very top
586 if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
587 listUnfadedTop += fadingEdge;
588 }
589 }
590
591 int childCount = getChildCount();
592 int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
593
594 if (showingBottomFadingEdge()) {
595 // leave room for bottom fading edge as long as rect isn't at very bottom
596 if ((mSelectedPosition < mItemCount - 1)
597 || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
598 listUnfadedBottom -= fadingEdge;
599 }
600 }
601
602 int scrollYDelta = 0;
603
604 if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
605 // need to MOVE DOWN to get it in view: move down just enough so
606 // that the entire rectangle is in view (or at least the first
607 // screen size chunk).
608
609 if (rect.height() > height) {
610 // just enough to get screen size chunk on
611 scrollYDelta += (rect.top - listUnfadedTop);
612 } else {
613 // get entire rect at bottom of screen
614 scrollYDelta += (rect.bottom - listUnfadedBottom);
615 }
616
617 // make sure we aren't scrolling beyond the end of our children
618 int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
619 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
620 } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
621 // need to MOVE UP to get it in view: move up just enough so that
622 // entire rectangle is in view (or at least the first screen
623 // size chunk of it).
624
625 if (rect.height() > height) {
626 // screen size chunk
627 scrollYDelta -= (listUnfadedBottom - rect.bottom);
628 } else {
629 // entire rect at top
630 scrollYDelta -= (listUnfadedTop - rect.top);
631 }
632
633 // make sure we aren't scrolling any further than the top our children
634 int top = getChildAt(0).getTop();
635 int deltaToTop = top - listUnfadedTop;
636 scrollYDelta = Math.max(scrollYDelta, deltaToTop);
637 }
638
639 final boolean scroll = scrollYDelta != 0;
640 if (scroll) {
641 scrollListItemsBy(-scrollYDelta);
Dianne Hackborn079e2352010-10-18 17:02:43 -0700642 positionSelector(INVALID_POSITION, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 mSelectedTop = child.getTop();
644 invalidate();
645 }
646 return scroll;
647 }
648
649 /**
650 * {@inheritDoc}
651 */
652 @Override
653 void fillGap(boolean down) {
654 final int count = getChildCount();
655 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800656 int paddingTop = 0;
657 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
658 paddingTop = getListPaddingTop();
659 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800660 final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800661 paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 fillDown(mFirstPosition + count, startOffset);
663 correctTooHigh(getChildCount());
664 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800665 int paddingBottom = 0;
666 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
667 paddingBottom = getListPaddingBottom();
668 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800670 getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 fillUp(mFirstPosition - 1, startOffset);
672 correctTooLow(getChildCount());
673 }
674 }
675
676 /**
677 * Fills the list from pos down to the end of the list view.
678 *
679 * @param pos The first position to put in the list
680 *
681 * @param nextTop The location where the top of the item associated with pos
682 * should be drawn
683 *
684 * @return The view that is currently selected, if it happens to be in the
685 * range that we draw.
686 */
687 private View fillDown(int pos, int nextTop) {
688 View selectedView = null;
689
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800690 int end = (mBottom - mTop);
691 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
692 end -= mListPadding.bottom;
693 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694
695 while (nextTop < end && pos < mItemCount) {
696 // is this the selected item?
697 boolean selected = pos == mSelectedPosition;
698 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
699
700 nextTop = child.getBottom() + mDividerHeight;
701 if (selected) {
702 selectedView = child;
703 }
704 pos++;
705 }
706
Adam Cohenb9673922012-01-05 13:58:47 -0800707 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 return selectedView;
709 }
710
711 /**
712 * Fills the list from pos up to the top of the list view.
713 *
714 * @param pos The first position to put in the list
715 *
716 * @param nextBottom The location where the bottom of the item associated
717 * with pos should be drawn
718 *
719 * @return The view that is currently selected
720 */
721 private View fillUp(int pos, int nextBottom) {
722 View selectedView = null;
723
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800724 int end = 0;
725 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
726 end = mListPadding.top;
727 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728
729 while (nextBottom > end && pos >= 0) {
730 // is this the selected item?
731 boolean selected = pos == mSelectedPosition;
732 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
733 nextBottom = child.getTop() - mDividerHeight;
734 if (selected) {
735 selectedView = child;
736 }
737 pos--;
738 }
739
740 mFirstPosition = pos + 1;
Adam Cohenb9673922012-01-05 13:58:47 -0800741 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 return selectedView;
743 }
744
745 /**
746 * Fills the list from top to bottom, starting with mFirstPosition
747 *
748 * @param nextTop The location where the top of the first item should be
749 * drawn
750 *
751 * @return The view that is currently selected
752 */
753 private View fillFromTop(int nextTop) {
754 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
755 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
756 if (mFirstPosition < 0) {
757 mFirstPosition = 0;
758 }
759 return fillDown(mFirstPosition, nextTop);
760 }
761
762
763 /**
764 * Put mSelectedPosition in the middle of the screen and then build up and
765 * down from there. This method forces mSelectedPosition to the center.
766 *
767 * @param childrenTop Top of the area in which children can be drawn, as
768 * measured in pixels
769 * @param childrenBottom Bottom of the area in which children can be drawn,
770 * as measured in pixels
771 * @return Currently selected view
772 */
773 private View fillFromMiddle(int childrenTop, int childrenBottom) {
774 int height = childrenBottom - childrenTop;
775
776 int position = reconcileSelectedPosition();
777
778 View sel = makeAndAddView(position, childrenTop, true,
779 mListPadding.left, true);
780 mFirstPosition = position;
781
782 int selHeight = sel.getMeasuredHeight();
783 if (selHeight <= height) {
784 sel.offsetTopAndBottom((height - selHeight) / 2);
785 }
786
787 fillAboveAndBelow(sel, position);
788
789 if (!mStackFromBottom) {
790 correctTooHigh(getChildCount());
791 } else {
792 correctTooLow(getChildCount());
793 }
794
795 return sel;
796 }
797
798 /**
799 * Once the selected view as been placed, fill up the visible area above and
800 * below it.
801 *
802 * @param sel The selected view
803 * @param position The position corresponding to sel
804 */
805 private void fillAboveAndBelow(View sel, int position) {
806 final int dividerHeight = mDividerHeight;
807 if (!mStackFromBottom) {
808 fillUp(position - 1, sel.getTop() - dividerHeight);
809 adjustViewsUpOrDown();
810 fillDown(position + 1, sel.getBottom() + dividerHeight);
811 } else {
812 fillDown(position + 1, sel.getBottom() + dividerHeight);
813 adjustViewsUpOrDown();
814 fillUp(position - 1, sel.getTop() - dividerHeight);
815 }
816 }
817
818
819 /**
820 * Fills the grid based on positioning the new selection at a specific
821 * location. The selection may be moved so that it does not intersect the
822 * faded edges. The grid is then filled upwards and downwards from there.
823 *
824 * @param selectedTop Where the selected item should be
825 * @param childrenTop Where to start drawing children
826 * @param childrenBottom Last pixel where children can be drawn
827 * @return The view that currently has selection
828 */
829 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
830 int fadingEdgeLength = getVerticalFadingEdgeLength();
831 final int selectedPosition = mSelectedPosition;
832
833 View sel;
834
835 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
836 selectedPosition);
837 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
838 selectedPosition);
839
840 sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
841
842
843 // Some of the newly selected item extends below the bottom of the list
844 if (sel.getBottom() > bottomSelectionPixel) {
845 // Find space available above the selection into which we can scroll
846 // upwards
847 final int spaceAbove = sel.getTop() - topSelectionPixel;
848
849 // Find space required to bring the bottom of the selected item
850 // fully into view
851 final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
852 final int offset = Math.min(spaceAbove, spaceBelow);
853
854 // Now offset the selected item to get it into view
855 sel.offsetTopAndBottom(-offset);
856 } else if (sel.getTop() < topSelectionPixel) {
857 // Find space required to bring the top of the selected item fully
858 // into view
859 final int spaceAbove = topSelectionPixel - sel.getTop();
860
861 // Find space available below the selection into which we can scroll
862 // downwards
863 final int spaceBelow = bottomSelectionPixel - sel.getBottom();
864 final int offset = Math.min(spaceAbove, spaceBelow);
865
866 // Offset the selected item to get it into view
867 sel.offsetTopAndBottom(offset);
868 }
869
870 // Fill in views above and below
871 fillAboveAndBelow(sel, selectedPosition);
872
873 if (!mStackFromBottom) {
874 correctTooHigh(getChildCount());
875 } else {
876 correctTooLow(getChildCount());
877 }
878
879 return sel;
880 }
881
882 /**
883 * Calculate the bottom-most pixel we can draw the selection into
884 *
885 * @param childrenBottom Bottom pixel were children can be drawn
886 * @param fadingEdgeLength Length of the fading edge in pixels, if present
887 * @param selectedPosition The position that will be selected
888 * @return The bottom-most pixel we can draw the selection into
889 */
890 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
891 int selectedPosition) {
892 int bottomSelectionPixel = childrenBottom;
893 if (selectedPosition != mItemCount - 1) {
894 bottomSelectionPixel -= fadingEdgeLength;
895 }
896 return bottomSelectionPixel;
897 }
898
899 /**
900 * Calculate the top-most pixel we can draw the selection into
901 *
902 * @param childrenTop Top pixel were children can be drawn
903 * @param fadingEdgeLength Length of the fading edge in pixels, if present
904 * @param selectedPosition The position that will be selected
905 * @return The top-most pixel we can draw the selection into
906 */
907 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
908 // first pixel we can draw the selection into
909 int topSelectionPixel = childrenTop;
910 if (selectedPosition > 0) {
911 topSelectionPixel += fadingEdgeLength;
912 }
913 return topSelectionPixel;
914 }
915
Winson Chung499cb9f2010-07-16 11:18:17 -0700916 /**
917 * Smoothly scroll to the specified adapter position. The view will
918 * scroll such that the indicated position is displayed.
919 * @param position Scroll to this adapter position.
920 */
921 @android.view.RemotableViewMethod
922 public void smoothScrollToPosition(int position) {
923 super.smoothScrollToPosition(position);
924 }
925
926 /**
927 * Smoothly scroll to the specified adapter position offset. The view will
928 * scroll such that the indicated position is displayed.
929 * @param offset The amount to offset from the adapter position to scroll to.
930 */
931 @android.view.RemotableViewMethod
932 public void smoothScrollByOffset(int offset) {
933 super.smoothScrollByOffset(offset);
934 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800935
936 /**
937 * Fills the list based on positioning the new selection relative to the old
938 * selection. The new selection will be placed at, above, or below the
939 * location of the new selection depending on how the selection is moving.
940 * The selection will then be pinned to the visible part of the screen,
941 * excluding the edges that are faded. The list is then filled upwards and
942 * downwards from there.
943 *
944 * @param oldSel The old selected view. Useful for trying to put the new
945 * selection in the same place
946 * @param newSel The view that is to become selected. Useful for trying to
947 * put the new selection in the same place
948 * @param delta Which way we are moving
949 * @param childrenTop Where to start drawing children
950 * @param childrenBottom Last pixel where children can be drawn
951 * @return The view that currently has selection
952 */
953 private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
954 int childrenBottom) {
955 int fadingEdgeLength = getVerticalFadingEdgeLength();
956 final int selectedPosition = mSelectedPosition;
957
958 View sel;
959
960 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
961 selectedPosition);
962 final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
963 selectedPosition);
964
965 if (delta > 0) {
966 /*
967 * Case 1: Scrolling down.
968 */
969
970 /*
971 * Before After
972 * | | | |
973 * +-------+ +-------+
974 * | A | | A |
975 * | 1 | => +-------+
976 * +-------+ | B |
977 * | B | | 2 |
978 * +-------+ +-------+
979 * | | | |
980 *
981 * Try to keep the top of the previously selected item where it was.
982 * oldSel = A
983 * sel = B
984 */
985
986 // Put oldSel (A) where it belongs
987 oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
988 mListPadding.left, false);
989
990 final int dividerHeight = mDividerHeight;
991
992 // Now put the new selection (B) below that
993 sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
994 mListPadding.left, true);
995
996 // Some of the newly selected item extends below the bottom of the list
997 if (sel.getBottom() > bottomSelectionPixel) {
998
999 // Find space available above the selection into which we can scroll upwards
1000 int spaceAbove = sel.getTop() - topSelectionPixel;
1001
1002 // Find space required to bring the bottom of the selected item fully into view
1003 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
1004
1005 // Don't scroll more than half the height of the list
1006 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1007 int offset = Math.min(spaceAbove, spaceBelow);
1008 offset = Math.min(offset, halfVerticalSpace);
1009
1010 // We placed oldSel, so offset that item
1011 oldSel.offsetTopAndBottom(-offset);
1012 // Now offset the selected item to get it into view
1013 sel.offsetTopAndBottom(-offset);
1014 }
1015
1016 // Fill in views above and below
1017 if (!mStackFromBottom) {
1018 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1019 adjustViewsUpOrDown();
1020 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1021 } else {
1022 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1023 adjustViewsUpOrDown();
1024 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1025 }
1026 } else if (delta < 0) {
1027 /*
1028 * Case 2: Scrolling up.
1029 */
1030
1031 /*
1032 * Before After
1033 * | | | |
1034 * +-------+ +-------+
1035 * | A | | A |
1036 * +-------+ => | 1 |
1037 * | B | +-------+
1038 * | 2 | | B |
1039 * +-------+ +-------+
1040 * | | | |
1041 *
1042 * Try to keep the top of the item about to become selected where it was.
1043 * newSel = A
1044 * olSel = B
1045 */
1046
1047 if (newSel != null) {
1048 // Try to position the top of newSel (A) where it was before it was selected
1049 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1050 true);
1051 } else {
1052 // If (A) was not on screen and so did not have a view, position
1053 // it above the oldSel (B)
1054 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1055 true);
1056 }
1057
1058 // Some of the newly selected item extends above the top of the list
1059 if (sel.getTop() < topSelectionPixel) {
1060 // Find space required to bring the top of the selected item fully into view
1061 int spaceAbove = topSelectionPixel - sel.getTop();
1062
1063 // Find space available below the selection into which we can scroll downwards
1064 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1065
1066 // Don't scroll more than half the height of the list
1067 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1068 int offset = Math.min(spaceAbove, spaceBelow);
1069 offset = Math.min(offset, halfVerticalSpace);
1070
1071 // Offset the selected item to get it into view
1072 sel.offsetTopAndBottom(offset);
1073 }
1074
1075 // Fill in views above and below
1076 fillAboveAndBelow(sel, selectedPosition);
1077 } else {
1078
1079 int oldTop = oldSel.getTop();
1080
1081 /*
1082 * Case 3: Staying still
1083 */
1084 sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1085
1086 // We're staying still...
1087 if (oldTop < childrenTop) {
1088 // ... but the top of the old selection was off screen.
1089 // (This can happen if the data changes size out from under us)
1090 int newBottom = sel.getBottom();
1091 if (newBottom < childrenTop + 20) {
1092 // Not enough visible -- bring it onscreen
1093 sel.offsetTopAndBottom(childrenTop - sel.getTop());
1094 }
1095 }
1096
1097 // Fill in views above and below
1098 fillAboveAndBelow(sel, selectedPosition);
1099 }
1100
1101 return sel;
1102 }
1103
Adam Powell9bf3c122010-02-26 11:32:07 -08001104 private class FocusSelector implements Runnable {
1105 private int mPosition;
1106 private int mPositionTop;
1107
1108 public FocusSelector setup(int position, int top) {
1109 mPosition = position;
1110 mPositionTop = top;
1111 return this;
1112 }
1113
1114 public void run() {
1115 setSelectionFromTop(mPosition, mPositionTop);
1116 }
1117 }
1118
1119 @Override
1120 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1121 if (getChildCount() > 0) {
1122 View focusedChild = getFocusedChild();
1123 if (focusedChild != null) {
1124 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1125 final int childBottom = focusedChild.getBottom();
1126 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1127 final int top = focusedChild.getTop() - offset;
1128 if (mFocusSelector == null) {
1129 mFocusSelector = new FocusSelector();
1130 }
1131 post(mFocusSelector.setup(childPosition, top));
1132 }
1133 }
1134 super.onSizeChanged(w, h, oldw, oldh);
1135 }
1136
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 @Override
1138 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1139 // Sets up mListPadding
1140 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1141
1142 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1143 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
1144 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1145 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1146
1147 int childWidth = 0;
1148 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001149 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150
1151 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
1152 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
1153 heightMode == MeasureSpec.UNSPECIFIED)) {
Romain Guy21875052010-01-06 18:48:08 -08001154 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155
1156 measureScrapChild(child, 0, widthMeasureSpec);
1157
1158 childWidth = child.getMeasuredWidth();
1159 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001160 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001161
Romain Guy9c3184cc2010-02-25 17:32:54 -08001162 if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1163 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001164 mRecycler.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 }
1166 }
1167
1168 if (widthMode == MeasureSpec.UNSPECIFIED) {
1169 widthSize = mListPadding.left + mListPadding.right + childWidth +
1170 getVerticalScrollbarWidth();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001171 } else {
1172 widthSize |= (childState&MEASURED_STATE_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 }
1174
1175 if (heightMode == MeasureSpec.UNSPECIFIED) {
1176 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1177 getVerticalFadingEdgeLength() * 2;
1178 }
1179
1180 if (heightMode == MeasureSpec.AT_MOST) {
1181 // TODO: after first layout we should maybe start at the first visible position, not 0
1182 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1183 }
1184
Dianne Hackborn189ee182010-12-02 21:48:53 -08001185 setMeasuredDimension(widthSize , heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 mWidthMeasureSpec = widthMeasureSpec;
1187 }
1188
1189 private void measureScrapChild(View child, int position, int widthMeasureSpec) {
1190 LayoutParams p = (LayoutParams) child.getLayoutParams();
1191 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001192 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001193 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 }
1195 p.viewType = mAdapter.getItemViewType(position);
Romain Guy0bf88592010-03-02 13:38:44 -08001196 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197
1198 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
1199 mListPadding.left + mListPadding.right, p.width);
1200 int lpHeight = p.height;
1201 int childHeightSpec;
1202 if (lpHeight > 0) {
1203 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1204 } else {
1205 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1206 }
1207 child.measure(childWidthSpec, childHeightSpec);
1208 }
1209
1210 /**
1211 * @return True to recycle the views used to measure this ListView in
1212 * UNSPECIFIED/AT_MOST modes, false otherwise.
1213 * @hide
1214 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001215 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 protected boolean recycleOnMeasure() {
1217 return true;
1218 }
1219
1220 /**
1221 * Measures the height of the given range of children (inclusive) and
1222 * returns the height with this ListView's padding and divider heights
1223 * included. If maxHeight is provided, the measuring will stop when the
1224 * current height reaches maxHeight.
1225 *
1226 * @param widthMeasureSpec The width measure spec to be given to a child's
1227 * {@link View#measure(int, int)}.
1228 * @param startPosition The position of the first child to be shown.
1229 * @param endPosition The (inclusive) position of the last child to be
1230 * shown. Specify {@link #NO_POSITION} if the last child should be
1231 * the last available child from the adapter.
1232 * @param maxHeight The maximum height that will be returned (if all the
1233 * children don't fit in this value, this value will be
1234 * returned).
1235 * @param disallowPartialChildPosition In general, whether the returned
1236 * height should only contain entire children. This is more
1237 * powerful--it is the first inclusive position at which partial
1238 * children will not be allowed. Example: it looks nice to have
1239 * at least 3 completely visible children, and in portrait this
1240 * will most likely fit; but in landscape there could be times
1241 * when even 2 children can not be completely shown, so a value
1242 * of 2 (remember, inclusive) would be good (assuming
1243 * startPosition is 0).
1244 * @return The height of this ListView with the given children.
1245 */
1246 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
1247 final int maxHeight, int disallowPartialChildPosition) {
1248
1249 final ListAdapter adapter = mAdapter;
1250 if (adapter == null) {
1251 return mListPadding.top + mListPadding.bottom;
1252 }
1253
1254 // Include the padding of the list
1255 int returnedHeight = mListPadding.top + mListPadding.bottom;
1256 final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
1257 // The previous height value that was less than maxHeight and contained
1258 // no partial children
1259 int prevHeightWithoutPartialChild = 0;
1260 int i;
1261 View child;
1262
1263 // mItemCount - 1 since endPosition parameter is inclusive
1264 endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1265 final AbsListView.RecycleBin recycleBin = mRecycler;
1266 final boolean recyle = recycleOnMeasure();
Romain Guy21875052010-01-06 18:48:08 -08001267 final boolean[] isScrap = mIsScrap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268
1269 for (i = startPosition; i <= endPosition; ++i) {
Romain Guy21875052010-01-06 18:48:08 -08001270 child = obtainView(i, isScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271
1272 measureScrapChild(child, i, widthMeasureSpec);
1273
1274 if (i > 0) {
1275 // Count the divider for all but one child
1276 returnedHeight += dividerHeight;
1277 }
1278
1279 // Recycle the view before we possibly return from the method
Romain Guy9c3184cc2010-02-25 17:32:54 -08001280 if (recyle && recycleBin.shouldRecycleViewType(
1281 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001282 recycleBin.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 }
1284
1285 returnedHeight += child.getMeasuredHeight();
1286
1287 if (returnedHeight >= maxHeight) {
1288 // We went over, figure out which height to return. If returnedHeight > maxHeight,
1289 // then the i'th position did not fit completely.
1290 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1291 && (i > disallowPartialChildPosition) // We've past the min pos
1292 && (prevHeightWithoutPartialChild > 0) // We have a prev height
1293 && (returnedHeight != maxHeight) // i'th child did not fit completely
1294 ? prevHeightWithoutPartialChild
1295 : maxHeight;
1296 }
1297
1298 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1299 prevHeightWithoutPartialChild = returnedHeight;
1300 }
1301 }
1302
1303 // At this point, we went through the range of children, and they each
1304 // completely fit, so return the returnedHeight
1305 return returnedHeight;
1306 }
1307
1308 @Override
1309 int findMotionRow(int y) {
1310 int childCount = getChildCount();
1311 if (childCount > 0) {
Adam Powell84222e02010-03-11 13:42:57 -08001312 if (!mStackFromBottom) {
1313 for (int i = 0; i < childCount; i++) {
1314 View v = getChildAt(i);
1315 if (y <= v.getBottom()) {
1316 return mFirstPosition + i;
1317 }
1318 }
1319 } else {
1320 for (int i = childCount - 1; i >= 0; i--) {
1321 View v = getChildAt(i);
1322 if (y >= v.getTop()) {
1323 return mFirstPosition + i;
1324 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 }
1326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 }
1328 return INVALID_POSITION;
1329 }
1330
1331 /**
1332 * Put a specific item at a specific location on the screen and then build
1333 * up and down from there.
1334 *
1335 * @param position The reference view to use as the starting point
1336 * @param top Pixel offset from the top of this view to the top of the
1337 * reference view.
1338 *
1339 * @return The selected view, or null if the selected view is outside the
1340 * visible area.
1341 */
1342 private View fillSpecific(int position, int top) {
1343 boolean tempIsSelected = position == mSelectedPosition;
1344 View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1345 // Possibly changed again in fillUp if we add rows above this one.
1346 mFirstPosition = position;
1347
1348 View above;
1349 View below;
1350
1351 final int dividerHeight = mDividerHeight;
1352 if (!mStackFromBottom) {
1353 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1354 // This will correct for the top of the first view not touching the top of the list
1355 adjustViewsUpOrDown();
1356 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1357 int childCount = getChildCount();
1358 if (childCount > 0) {
1359 correctTooHigh(childCount);
1360 }
1361 } else {
1362 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1363 // This will correct for the bottom of the last view not touching the bottom of the list
1364 adjustViewsUpOrDown();
1365 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1366 int childCount = getChildCount();
1367 if (childCount > 0) {
1368 correctTooLow(childCount);
1369 }
1370 }
1371
1372 if (tempIsSelected) {
1373 return temp;
1374 } else if (above != null) {
1375 return above;
1376 } else {
1377 return below;
1378 }
1379 }
1380
1381 /**
1382 * Check if we have dragged the bottom of the list too high (we have pushed the
1383 * top element off the top of the screen when we did not need to). Correct by sliding
1384 * everything back down.
1385 *
1386 * @param childCount Number of children
1387 */
1388 private void correctTooHigh(int childCount) {
1389 // First see if the last item is visible. If it is not, it is OK for the
1390 // top of the list to be pushed up.
1391 int lastPosition = mFirstPosition + childCount - 1;
1392 if (lastPosition == mItemCount - 1 && childCount > 0) {
1393
1394 // Get the last child ...
1395 final View lastChild = getChildAt(childCount - 1);
1396
1397 // ... and its bottom edge
1398 final int lastBottom = lastChild.getBottom();
1399
1400 // This is bottom of our drawable area
1401 final int end = (mBottom - mTop) - mListPadding.bottom;
1402
1403 // This is how far the bottom edge of the last view is from the bottom of the
1404 // drawable area
1405 int bottomOffset = end - lastBottom;
1406 View firstChild = getChildAt(0);
1407 final int firstTop = firstChild.getTop();
1408
1409 // Make sure we are 1) Too high, and 2) Either there are more rows above the
1410 // first row or the first row is scrolled off the top of the drawable area
1411 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
1412 if (mFirstPosition == 0) {
1413 // Don't pull the top too far down
1414 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1415 }
1416 // Move everything down
1417 offsetChildrenTopAndBottom(bottomOffset);
1418 if (mFirstPosition > 0) {
1419 // Fill the gap that was opened above mFirstPosition with more rows, if
1420 // possible
1421 fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1422 // Close up the remaining gap
1423 adjustViewsUpOrDown();
1424 }
1425
1426 }
1427 }
1428 }
1429
1430 /**
1431 * Check if we have dragged the bottom of the list too low (we have pushed the
1432 * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1433 * everything back up.
1434 *
1435 * @param childCount Number of children
1436 */
1437 private void correctTooLow(int childCount) {
1438 // First see if the first item is visible. If it is not, it is OK for the
1439 // bottom of the list to be pushed down.
1440 if (mFirstPosition == 0 && childCount > 0) {
1441
1442 // Get the first child ...
1443 final View firstChild = getChildAt(0);
1444
1445 // ... and its top edge
1446 final int firstTop = firstChild.getTop();
1447
1448 // This is top of our drawable area
1449 final int start = mListPadding.top;
1450
1451 // This is bottom of our drawable area
1452 final int end = (mBottom - mTop) - mListPadding.bottom;
1453
1454 // This is how far the top edge of the first view is from the top of the
1455 // drawable area
1456 int topOffset = firstTop - start;
1457 View lastChild = getChildAt(childCount - 1);
1458 final int lastBottom = lastChild.getBottom();
1459 int lastPosition = mFirstPosition + childCount - 1;
1460
1461 // Make sure we are 1) Too low, and 2) Either there are more rows below the
1462 // last row or the last row is scrolled off the bottom of the drawable area
Romain Guy6198ae82009-08-31 17:45:55 -07001463 if (topOffset > 0) {
1464 if (lastPosition < mItemCount - 1 || lastBottom > end) {
1465 if (lastPosition == mItemCount - 1) {
1466 // Don't pull the bottom too far up
1467 topOffset = Math.min(topOffset, lastBottom - end);
1468 }
1469 // Move everything up
1470 offsetChildrenTopAndBottom(-topOffset);
1471 if (lastPosition < mItemCount - 1) {
1472 // Fill the gap that was opened below the last position with more rows, if
1473 // possible
1474 fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1475 // Close up the remaining gap
1476 adjustViewsUpOrDown();
1477 }
1478 } else if (lastPosition == mItemCount - 1) {
1479 adjustViewsUpOrDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001480 }
1481 }
1482 }
1483 }
1484
1485 @Override
1486 protected void layoutChildren() {
1487 final boolean blockLayoutRequests = mBlockLayoutRequests;
Alan Viveretted44696c2013-07-18 10:37:15 -07001488 if (blockLayoutRequests) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001489 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001490 }
1491
Alan Viveretted44696c2013-07-18 10:37:15 -07001492 mBlockLayoutRequests = true;
1493
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001494 try {
1495 super.layoutChildren();
1496
1497 invalidate();
1498
1499 if (mAdapter == null) {
1500 resetList();
1501 invokeOnItemScrollListener();
1502 return;
1503 }
1504
Alan Viveretted44696c2013-07-18 10:37:15 -07001505 final int childrenTop = mListPadding.top;
1506 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1507 final int childCount = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508
Romain Guyead0d4d2009-12-08 17:33:53 -08001509 int index = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001510 int delta = 0;
1511
1512 View sel;
1513 View oldSel = null;
1514 View oldFirst = null;
1515 View newSel = null;
1516
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 // Remember stuff we will need down below
1518 switch (mLayoutMode) {
1519 case LAYOUT_SET_SELECTION:
1520 index = mNextSelectedPosition - mFirstPosition;
1521 if (index >= 0 && index < childCount) {
1522 newSel = getChildAt(index);
1523 }
1524 break;
1525 case LAYOUT_FORCE_TOP:
1526 case LAYOUT_FORCE_BOTTOM:
1527 case LAYOUT_SPECIFIC:
1528 case LAYOUT_SYNC:
1529 break;
1530 case LAYOUT_MOVE_SELECTION:
1531 default:
1532 // Remember the previously selected view
1533 index = mSelectedPosition - mFirstPosition;
1534 if (index >= 0 && index < childCount) {
1535 oldSel = getChildAt(index);
1536 }
1537
1538 // Remember the previous first child
1539 oldFirst = getChildAt(0);
1540
1541 if (mNextSelectedPosition >= 0) {
1542 delta = mNextSelectedPosition - mSelectedPosition;
1543 }
1544
1545 // Caution: newSel might be null
1546 newSel = getChildAt(index + delta);
1547 }
1548
1549
1550 boolean dataChanged = mDataChanged;
1551 if (dataChanged) {
1552 handleDataChanged();
1553 }
1554
1555 // Handle the empty set by removing all views that are visible
1556 // and calling it a day
1557 if (mItemCount == 0) {
1558 resetList();
1559 invokeOnItemScrollListener();
1560 return;
Romain Guyb45f1242009-03-24 21:30:00 -07001561 } else if (mItemCount != mAdapter.getCount()) {
1562 throw new IllegalStateException("The content of the adapter has changed but "
1563 + "ListView did not receive a notification. Make sure the content of "
Alan Viverette7d8314d2013-09-10 11:22:53 -07001564 + "your adapter is not modified from a background thread, but only from "
1565 + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
1566 + "when its content changes. [in ListView(" + getId() + ", " + getClass()
Owen Lin3940f2d2009-08-13 15:21:16 +08001567 + ") with Adapter(" + mAdapter.getClass() + ")]");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 }
1569
1570 setSelectedPositionInt(mNextSelectedPosition);
1571
Alan Viverette3e141622014-02-18 17:05:13 -08001572 AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
1573 View accessibilityFocusLayoutRestoreView = null;
1574 int accessibilityFocusPosition = INVALID_POSITION;
1575
1576 // Remember which child, if any, had accessibility focus. This must
1577 // occur before recycling any views, since that will clear
1578 // accessibility focus.
1579 final ViewRootImpl viewRootImpl = getViewRootImpl();
1580 if (viewRootImpl != null) {
1581 final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
1582 if (focusHost != null) {
1583 final View focusChild = getAccessibilityFocusedChild(focusHost);
1584 if (focusChild != null) {
1585 if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
1586 || focusChild.hasTransientState() || mAdapterHasStableIds) {
1587 // The views won't be changing, so try to maintain
1588 // focus on the current host and virtual view.
1589 accessibilityFocusLayoutRestoreView = focusHost;
1590 accessibilityFocusLayoutRestoreNode = viewRootImpl
1591 .getAccessibilityFocusedVirtualView();
1592 }
1593
1594 // If all else fails, maintain focus at the same
1595 // position.
1596 accessibilityFocusPosition = getPositionForView(focusChild);
1597 }
1598 }
Alan Viveretteb53c5f62013-03-15 15:50:37 -07001599 }
1600
Alan Viverette3e141622014-02-18 17:05:13 -08001601 View focusLayoutRestoreDirectChild = null;
1602 View focusLayoutRestoreView = null;
1603
1604 // Take focus back to us temporarily to avoid the eventual call to
1605 // clear focus when removing the focused child below from messing
1606 // things up when ViewAncestor assigns focus back to someone else.
Alan Viveretted44696c2013-07-18 10:37:15 -07001607 final View focusedChild = getFocusedChild();
1608 if (focusedChild != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001609 // TODO: in some cases focusedChild.getParent() == null
1610
1611 // We can remember the focused view to restore after re-layout
1612 // if the data hasn't changed, or if the focused position is a
1613 // header or footer.
1614 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
1615 focusLayoutRestoreDirectChild = focusedChild;
1616 // Remember the specific view that had focus.
1617 focusLayoutRestoreView = findFocus();
1618 if (focusLayoutRestoreView != null) {
1619 // Tell it we are going to mess with it.
1620 focusLayoutRestoreView.onStartTemporaryDetach();
1621 }
1622 }
1623 requestFocus();
Alan Viveretted44696c2013-07-18 10:37:15 -07001624 }
1625
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001626 // Pull all children into the RecycleBin.
1627 // These views will be reused if possible
1628 final int firstPosition = mFirstPosition;
1629 final RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001630 if (dataChanged) {
1631 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001632 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001633 }
1634 } else {
1635 recycleBin.fillActiveViews(childCount, firstPosition);
1636 }
1637
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001638 // Clear out old views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001639 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001640 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001641
1642 switch (mLayoutMode) {
1643 case LAYOUT_SET_SELECTION:
1644 if (newSel != null) {
1645 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1646 } else {
1647 sel = fillFromMiddle(childrenTop, childrenBottom);
1648 }
1649 break;
1650 case LAYOUT_SYNC:
1651 sel = fillSpecific(mSyncPosition, mSpecificTop);
1652 break;
1653 case LAYOUT_FORCE_BOTTOM:
1654 sel = fillUp(mItemCount - 1, childrenBottom);
1655 adjustViewsUpOrDown();
1656 break;
1657 case LAYOUT_FORCE_TOP:
1658 mFirstPosition = 0;
1659 sel = fillFromTop(childrenTop);
1660 adjustViewsUpOrDown();
1661 break;
1662 case LAYOUT_SPECIFIC:
1663 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
1664 break;
1665 case LAYOUT_MOVE_SELECTION:
1666 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1667 break;
1668 default:
1669 if (childCount == 0) {
1670 if (!mStackFromBottom) {
1671 final int position = lookForSelectablePosition(0, true);
1672 setSelectedPositionInt(position);
1673 sel = fillFromTop(childrenTop);
1674 } else {
1675 final int position = lookForSelectablePosition(mItemCount - 1, false);
1676 setSelectedPositionInt(position);
1677 sel = fillUp(mItemCount - 1, childrenBottom);
1678 }
1679 } else {
1680 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1681 sel = fillSpecific(mSelectedPosition,
1682 oldSel == null ? childrenTop : oldSel.getTop());
1683 } else if (mFirstPosition < mItemCount) {
1684 sel = fillSpecific(mFirstPosition,
1685 oldFirst == null ? childrenTop : oldFirst.getTop());
1686 } else {
1687 sel = fillSpecific(0, childrenTop);
1688 }
1689 }
1690 break;
1691 }
1692
1693 // Flush any cached views that did not get reused above
1694 recycleBin.scrapActiveViews();
1695
1696 if (sel != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001697 // The current selected item should get focus if items are
1698 // focusable.
1699 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1700 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1701 focusLayoutRestoreView != null &&
1702 focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1703 if (!focusWasTaken) {
1704 // Selected item didn't take focus, but we still want to
1705 // make sure something else outside of the selected view
1706 // has focus.
Romain Guy3616a412009-09-15 13:50:37 -07001707 final View focused = getFocusedChild();
1708 if (focused != null) {
1709 focused.clearFocus();
1710 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001711 positionSelector(INVALID_POSITION, sel);
Alan Viverette3e141622014-02-18 17:05:13 -08001712 } else {
1713 sel.setSelected(false);
1714 mSelectorRect.setEmpty();
Romain Guy3616a412009-09-15 13:50:37 -07001715 }
1716 } else {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001717 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001718 }
1719 mSelectedTop = sel.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001720 } else {
Alan Viveretted44696c2013-07-18 10:37:15 -07001721 // If the user's finger is down, select the motion position.
1722 // Otherwise, clear selection.
1723 if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
1724 final View child = getChildAt(mMotionPosition - mFirstPosition);
1725 if (child != null) {
1726 positionSelector(mMotionPosition, child);
1727 }
Romain Guy3616a412009-09-15 13:50:37 -07001728 } else {
1729 mSelectedTop = 0;
1730 mSelectorRect.setEmpty();
1731 }
Alan Viverette3e141622014-02-18 17:05:13 -08001732
1733 // Even if there is not selected position, we may need to
1734 // restore focus (i.e. something focusable in touch mode).
1735 if (hasFocus() && focusLayoutRestoreView != null) {
1736 focusLayoutRestoreView.requestFocus();
1737 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001738 }
1739
Alan Viverette3e141622014-02-18 17:05:13 -08001740 // Attempt to restore accessibility focus, if necessary.
Alan Viverette2e6fc8c2014-02-24 11:09:18 -08001741 if (viewRootImpl != null) {
1742 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1743 if (newAccessibilityFocusedView == null) {
1744 if (accessibilityFocusLayoutRestoreView != null
1745 && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
1746 final AccessibilityNodeProvider provider =
1747 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1748 if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
1749 final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1750 accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1751 provider.performAction(virtualViewId,
1752 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1753 } else {
1754 accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1755 }
1756 } else if (accessibilityFocusPosition != INVALID_POSITION) {
1757 // Bound the position within the visible children.
1758 final int position = MathUtils.constrain(
1759 accessibilityFocusPosition - mFirstPosition, 0,
1760 getChildCount() - 1);
1761 final View restoreView = getChildAt(position);
1762 if (restoreView != null) {
1763 restoreView.requestAccessibilityFocus();
1764 }
Alan Viverette68207512013-08-22 12:33:07 -07001765 }
alanv30ee76c2012-09-07 10:31:16 -07001766 }
1767 }
1768
Alan Viverette3e141622014-02-18 17:05:13 -08001769 // Tell focus view we are done mucking with it, if it is still in
1770 // our view hierarchy.
1771 if (focusLayoutRestoreView != null
1772 && focusLayoutRestoreView.getWindowToken() != null) {
1773 focusLayoutRestoreView.onFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001774 }
1775
1776 mLayoutMode = LAYOUT_NORMAL;
1777 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001778 if (mPositionScrollAfterLayout != null) {
1779 post(mPositionScrollAfterLayout);
1780 mPositionScrollAfterLayout = null;
1781 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 mNeedSync = false;
1783 setNextSelectedPositionInt(mSelectedPosition);
1784
1785 updateScrollIndicators();
1786
1787 if (mItemCount > 0) {
1788 checkSelectionChanged();
1789 }
1790
1791 invokeOnItemScrollListener();
1792 } finally {
1793 if (!blockLayoutRequests) {
1794 mBlockLayoutRequests = false;
1795 }
1796 }
1797 }
1798
1799 /**
Alan Viverette3e141622014-02-18 17:05:13 -08001800 * @param child a direct child of this list.
1801 * @return Whether child is a header or footer view.
1802 */
1803 private boolean isDirectChildHeaderOrFooter(View child) {
1804 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1805 final int numHeaders = headers.size();
1806 for (int i = 0; i < numHeaders; i++) {
1807 if (child == headers.get(i).view) {
1808 return true;
1809 }
1810 }
1811
1812 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1813 final int numFooters = footers.size();
1814 for (int i = 0; i < numFooters; i++) {
1815 if (child == footers.get(i).view) {
1816 return true;
1817 }
1818 }
1819
1820 return false;
1821 }
1822
1823 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001824 * Obtain the view and add it to our list of children. The view can be made
1825 * fresh, converted from an unused view, or used as is if it was in the
1826 * recycle bin.
1827 *
1828 * @param position Logical position in the list
1829 * @param y Top or bottom edge of the view to add
1830 * @param flow If flow is true, align top edge to y. If false, align bottom
1831 * edge to y.
1832 * @param childrenLeft Left edge where children should be positioned
1833 * @param selected Is this position selected?
1834 * @return View that was added
1835 */
1836 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1837 boolean selected) {
1838 View child;
1839
1840
1841 if (!mDataChanged) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001842 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001843 child = mRecycler.getActiveView(position);
1844 if (child != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001845 // Found it -- we're using an existing child
1846 // This just needs to be positioned
1847 setupChild(child, position, y, flow, childrenLeft, selected, true);
1848
1849 return child;
1850 }
1851 }
1852
1853 // Make a new view for this position, or convert an unused view if possible
Romain Guy21875052010-01-06 18:48:08 -08001854 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001855
1856 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001857 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001858
1859 return child;
1860 }
1861
1862 /**
1863 * Add a view as a child and make sure it is measured (if necessary) and
1864 * positioned properly.
1865 *
1866 * @param child The view to add
1867 * @param position The position of this child
1868 * @param y The y position relative to which this view will be positioned
1869 * @param flowDown If true, align top edge to y. If false, align bottom
1870 * edge to y.
1871 * @param childrenLeft Left edge where children should be positioned
1872 * @param selected Is this position selected?
1873 * @param recycled Has this view been pulled from the recycle bin? If so it
1874 * does not need to be remeasured.
1875 */
1876 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1877 boolean selected, boolean recycled) {
Romain Guy5fade8c2013-07-10 16:36:18 -07001878 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
1879
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001880 final boolean isSelected = selected && shouldShowSelector();
1881 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001882 final int mode = mTouchMode;
1883 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1884 mMotionPosition == position;
1885 final boolean updateChildPressed = isPressed != child.isPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001886 final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1887
1888 // Respect layout params that are already in the view. Otherwise make some up...
1889 // noinspection unchecked
1890 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1891 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001892 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001893 }
1894 p.viewType = mAdapter.getItemViewType(position);
1895
Romain Guy0bf88592010-03-02 13:38:44 -08001896 if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001897 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001898 attachViewToParent(child, flowDown ? -1 : 0, p);
1899 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001900 p.forceAdd = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001901 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1902 p.recycledHeaderFooter = true;
1903 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001904 addViewInLayout(child, flowDown ? -1 : 0, p, true);
1905 }
1906
1907 if (updateChildSelected) {
1908 child.setSelected(isSelected);
1909 }
1910
Romain Guy3616a412009-09-15 13:50:37 -07001911 if (updateChildPressed) {
1912 child.setPressed(isPressed);
1913 }
1914
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001915 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1916 if (child instanceof Checkable) {
1917 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001918 } else if (getContext().getApplicationInfo().targetSdkVersion
1919 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1920 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001921 }
1922 }
1923
1924 if (needToMeasure) {
1925 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
1926 mListPadding.left + mListPadding.right, p.width);
1927 int lpHeight = p.height;
1928 int childHeightSpec;
1929 if (lpHeight > 0) {
1930 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1931 } else {
1932 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1933 }
1934 child.measure(childWidthSpec, childHeightSpec);
1935 } else {
1936 cleanupLayoutState(child);
1937 }
1938
1939 final int w = child.getMeasuredWidth();
1940 final int h = child.getMeasuredHeight();
1941 final int childTop = flowDown ? y : y - h;
1942
1943 if (needToMeasure) {
1944 final int childRight = childrenLeft + w;
1945 final int childBottom = childTop + h;
1946 child.layout(childrenLeft, childTop, childRight, childBottom);
1947 } else {
1948 child.offsetLeftAndRight(childrenLeft - child.getLeft());
1949 child.offsetTopAndBottom(childTop - child.getTop());
1950 }
1951
1952 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1953 child.setDrawingCacheEnabled(true);
1954 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001955
1956 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1957 != position) {
1958 child.jumpDrawablesToCurrentState();
1959 }
Romain Guy5fade8c2013-07-10 16:36:18 -07001960
1961 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001962 }
1963
1964 @Override
1965 protected boolean canAnimate() {
1966 return super.canAnimate() && mItemCount > 0;
1967 }
1968
1969 /**
1970 * Sets the currently selected item. If in touch mode, the item will not be selected
1971 * but it will still be positioned appropriately. If the specified selection position
1972 * is less than 0, then the item at position 0 will be selected.
1973 *
1974 * @param position Index (starting at 0) of the data item to be selected.
1975 */
1976 @Override
1977 public void setSelection(int position) {
1978 setSelectionFromTop(position, 0);
1979 }
1980
1981 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001982 * Makes the item at the supplied position selected.
Mike Cleronf116bf82009-09-27 19:14:12 -07001983 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001984 * @param position the position of the item to select
1985 */
1986 @Override
1987 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001988 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07001989 boolean awakeScrollbars = false;
1990
1991 final int selectedPosition = mSelectedPosition;
1992
1993 if (selectedPosition >= 0) {
1994 if (position == selectedPosition - 1) {
1995 awakeScrollbars = true;
1996 } else if (position == selectedPosition + 1) {
1997 awakeScrollbars = true;
1998 }
1999 }
2000
Adam Powell1fa179e2012-04-12 15:01:40 -07002001 if (mPositionScroller != null) {
2002 mPositionScroller.stop();
2003 }
2004
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002005 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07002006
2007 if (awakeScrollbars) {
2008 awakenScrollBars();
2009 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002010 }
2011
2012 /**
2013 * Find a position that can be selected (i.e., is not a separator).
2014 *
2015 * @param position The starting position to look at.
2016 * @param lookDown Whether to look down for other positions.
2017 * @return The next selectable position starting at position and then searching either up or
2018 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
2019 */
2020 @Override
2021 int lookForSelectablePosition(int position, boolean lookDown) {
2022 final ListAdapter adapter = mAdapter;
2023 if (adapter == null || isInTouchMode()) {
2024 return INVALID_POSITION;
2025 }
2026
2027 final int count = adapter.getCount();
2028 if (!mAreAllItemsSelectable) {
2029 if (lookDown) {
2030 position = Math.max(0, position);
2031 while (position < count && !adapter.isEnabled(position)) {
2032 position++;
2033 }
2034 } else {
2035 position = Math.min(position, count - 1);
2036 while (position >= 0 && !adapter.isEnabled(position)) {
2037 position--;
2038 }
2039 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002040 }
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002041
2042 if (position < 0 || position >= count) {
2043 return INVALID_POSITION;
2044 }
2045
2046 return position;
2047 }
2048
2049 /**
2050 * Find a position that can be selected (i.e., is not a separator). If there
2051 * are no selectable positions in the specified direction from the starting
2052 * position, searches in the opposite direction from the starting position
2053 * to the current position.
2054 *
2055 * @param current the current position
2056 * @param position the starting position
2057 * @param lookDown whether to look down for other positions
2058 * @return the next selectable position, or {@link #INVALID_POSITION} if
2059 * nothing can be found
2060 */
2061 int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2062 final ListAdapter adapter = mAdapter;
2063 if (adapter == null || isInTouchMode()) {
2064 return INVALID_POSITION;
2065 }
2066
2067 // First check after the starting position in the specified direction.
2068 final int after = lookForSelectablePosition(position, lookDown);
2069 if (after != INVALID_POSITION) {
2070 return after;
2071 }
2072
2073 // Then check between the starting position and the current position.
2074 final int count = adapter.getCount();
2075 current = MathUtils.constrain(current, -1, count - 1);
2076 if (lookDown) {
2077 position = Math.min(position - 1, count - 1);
2078 while ((position > current) && !adapter.isEnabled(position)) {
2079 position--;
2080 }
2081 if (position <= current) {
2082 return INVALID_POSITION;
2083 }
2084 } else {
2085 position = Math.max(0, position + 1);
2086 while ((position < current) && !adapter.isEnabled(position)) {
2087 position++;
2088 }
2089 if (position >= current) {
2090 return INVALID_POSITION;
2091 }
2092 }
2093
2094 return position;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002095 }
2096
2097 /**
2098 * setSelectionAfterHeaderView set the selection to be the first list item
2099 * after the header views.
2100 */
2101 public void setSelectionAfterHeaderView() {
2102 final int count = mHeaderViewInfos.size();
2103 if (count > 0) {
2104 mNextSelectedPosition = 0;
2105 return;
2106 }
2107
2108 if (mAdapter != null) {
2109 setSelection(count);
2110 } else {
2111 mNextSelectedPosition = count;
2112 mLayoutMode = LAYOUT_SET_SELECTION;
2113 }
2114
2115 }
2116
2117 @Override
2118 public boolean dispatchKeyEvent(KeyEvent event) {
2119 // Dispatch in the normal way
2120 boolean handled = super.dispatchKeyEvent(event);
2121 if (!handled) {
2122 // If we didn't handle it...
2123 View focused = getFocusedChild();
2124 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2125 // ... and our focused child didn't handle it
2126 // ... give it to ourselves so we can scroll if necessary
2127 handled = onKeyDown(event.getKeyCode(), event);
2128 }
2129 }
2130 return handled;
2131 }
2132
2133 @Override
2134 public boolean onKeyDown(int keyCode, KeyEvent event) {
2135 return commonKey(keyCode, 1, event);
2136 }
2137
2138 @Override
2139 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2140 return commonKey(keyCode, repeatCount, event);
2141 }
2142
2143 @Override
2144 public boolean onKeyUp(int keyCode, KeyEvent event) {
2145 return commonKey(keyCode, 1, event);
2146 }
2147
2148 private boolean commonKey(int keyCode, int count, KeyEvent event) {
Adam Powell31986b52013-09-24 14:53:30 -07002149 if (mAdapter == null || !isAttachedToWindow()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002150 return false;
2151 }
2152
2153 if (mDataChanged) {
2154 layoutChildren();
2155 }
2156
2157 boolean handled = false;
2158 int action = event.getAction();
2159
2160 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002161 switch (keyCode) {
2162 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002163 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002164 handled = resurrectSelectionIfNeeded();
2165 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002166 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002167 if (arrowScroll(FOCUS_UP)) {
2168 handled = true;
2169 } else {
2170 break;
2171 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002173 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002174 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002175 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002176 }
2177 break;
2178
2179 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002180 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002181 handled = resurrectSelectionIfNeeded();
2182 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002183 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002184 if (arrowScroll(FOCUS_DOWN)) {
2185 handled = true;
2186 } else {
2187 break;
2188 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002189 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002190 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002191 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002192 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002193 }
2194 break;
2195
2196 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002197 if (event.hasNoModifiers()) {
2198 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002201
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002202 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002203 if (event.hasNoModifiers()) {
2204 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2205 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002206 break;
2207
2208 case KeyEvent.KEYCODE_DPAD_CENTER:
2209 case KeyEvent.KEYCODE_ENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002210 if (event.hasNoModifiers()) {
2211 handled = resurrectSelectionIfNeeded();
2212 if (!handled
2213 && event.getRepeatCount() == 0 && getChildCount() > 0) {
2214 keyPressed();
2215 handled = true;
2216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002217 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002218 break;
2219
2220 case KeyEvent.KEYCODE_SPACE:
2221 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002222 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002223 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002224 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002225 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002226 }
2227 handled = true;
2228 }
2229 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002230
2231 case KeyEvent.KEYCODE_PAGE_UP:
2232 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002233 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002234 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002235 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002236 }
2237 break;
2238
2239 case KeyEvent.KEYCODE_PAGE_DOWN:
2240 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002241 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002242 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002243 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002244 }
2245 break;
2246
2247 case KeyEvent.KEYCODE_MOVE_HOME:
2248 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002249 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002250 }
2251 break;
2252
2253 case KeyEvent.KEYCODE_MOVE_END:
2254 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002255 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002256 }
2257 break;
2258
2259 case KeyEvent.KEYCODE_TAB:
2260 // XXX Sometimes it is useful to be able to TAB through the items in
2261 // a ListView sequentially. Unfortunately this can create an
2262 // asymmetry in TAB navigation order unless the list selection
2263 // always reverts to the top or bottom when receiving TAB focus from
2264 // another widget. Leaving this behavior disabled for now but
2265 // perhaps it should be configurable (and more comprehensive).
2266 if (false) {
2267 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002268 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002269 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002270 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002271 }
2272 }
2273 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002274 }
2275 }
2276
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002277 if (handled) {
2278 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002279 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002280
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002281 if (sendToTextFilter(keyCode, count, event)) {
2282 return true;
2283 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002284
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002285 switch (action) {
2286 case KeyEvent.ACTION_DOWN:
2287 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002288
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002289 case KeyEvent.ACTION_UP:
2290 return super.onKeyUp(keyCode, event);
2291
2292 case KeyEvent.ACTION_MULTIPLE:
2293 return super.onKeyMultiple(keyCode, count, event);
2294
2295 default: // shouldn't happen
2296 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002297 }
2298 }
2299
2300 /**
2301 * Scrolls up or down by the number of items currently present on screen.
2302 *
2303 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2304 * @return whether selection was moved
2305 */
2306 boolean pageScroll(int direction) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002307 final int nextPage;
2308 final boolean down;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002309
2310 if (direction == FOCUS_UP) {
2311 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002312 down = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002313 } else if (direction == FOCUS_DOWN) {
2314 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2315 down = true;
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002316 } else {
2317 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002318 }
2319
2320 if (nextPage >= 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002321 final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002322 if (position >= 0) {
2323 mLayoutMode = LAYOUT_SPECIFIC;
2324 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2325
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002326 if (down && (position > (mItemCount - getChildCount()))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002327 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2328 }
2329
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002330 if (!down && (position < getChildCount())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002331 mLayoutMode = LAYOUT_FORCE_TOP;
2332 }
2333
2334 setSelectionInt(position);
2335 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002336 if (!awakenScrollBars()) {
2337 invalidate();
2338 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002339
2340 return true;
2341 }
2342 }
2343
2344 return false;
2345 }
2346
2347 /**
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002348 * Go to the last or first item if possible (not worrying about panning
2349 * across or navigating within the internal focus of the currently selected
2350 * item.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002351 *
2352 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002353 * @return whether selection was moved
2354 */
2355 boolean fullScroll(int direction) {
2356 boolean moved = false;
2357 if (direction == FOCUS_UP) {
2358 if (mSelectedPosition != 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002359 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002360 if (position >= 0) {
2361 mLayoutMode = LAYOUT_FORCE_TOP;
2362 setSelectionInt(position);
2363 invokeOnItemScrollListener();
2364 }
2365 moved = true;
2366 }
2367 } else if (direction == FOCUS_DOWN) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002368 final int lastItem = (mItemCount - 1);
2369 if (mSelectedPosition < lastItem) {
2370 final int position = lookForSelectablePositionAfter(
2371 mSelectedPosition, lastItem, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002372 if (position >= 0) {
2373 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2374 setSelectionInt(position);
2375 invokeOnItemScrollListener();
2376 }
2377 moved = true;
2378 }
2379 }
2380
Mike Cleronf116bf82009-09-27 19:14:12 -07002381 if (moved && !awakenScrollBars()) {
2382 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002383 invalidate();
2384 }
2385
2386 return moved;
2387 }
2388
2389 /**
2390 * To avoid horizontal focus searches changing the selected item, we
2391 * manually focus search within the selected item (as applicable), and
2392 * prevent focus from jumping to something within another item.
2393 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2394 * @return Whether this consumes the key event.
2395 */
2396 private boolean handleHorizontalFocusWithinListItem(int direction) {
2397 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002398 throw new IllegalArgumentException("direction must be one of"
2399 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002400 }
2401
2402 final int numChildren = getChildCount();
2403 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2404 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002405 if (selectedView != null && selectedView.hasFocus() &&
2406 selectedView instanceof ViewGroup) {
2407
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002408 final View currentFocus = selectedView.findFocus();
2409 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002410 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002411 if (nextFocus != null) {
2412 // do the math to get interesting rect in next focus' coordinates
2413 currentFocus.getFocusedRect(mTempRect);
2414 offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2415 offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2416 if (nextFocus.requestFocus(direction, mTempRect)) {
2417 return true;
2418 }
2419 }
2420 // we are blocking the key from being handled (by returning true)
2421 // if the global result is going to be some other view within this
2422 // list. this is to acheive the overall goal of having
2423 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002424 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2425 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002426 if (globalNextFocus != null) {
2427 return isViewAncestorOf(globalNextFocus, this);
2428 }
2429 }
2430 }
2431 return false;
2432 }
2433
2434 /**
2435 * Scrolls to the next or previous item if possible.
2436 *
2437 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2438 *
2439 * @return whether selection was moved
2440 */
2441 boolean arrowScroll(int direction) {
2442 try {
2443 mInLayout = true;
2444 final boolean handled = arrowScrollImpl(direction);
2445 if (handled) {
2446 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2447 }
2448 return handled;
2449 } finally {
2450 mInLayout = false;
2451 }
2452 }
2453
2454 /**
Adam Powell2a939112013-03-21 17:08:38 -07002455 * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002456 * to move to. This return a position in the direction given if the selected item
2457 * is fully visible.
Adam Powell2a939112013-03-21 17:08:38 -07002458 *
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002459 * @param selectedView Current selected view to move from
Adam Powell2a939112013-03-21 17:08:38 -07002460 * @param selectedPos Current selected position to move from
2461 * @param direction Direction to move in
2462 * @return Desired selected position after moving in the given direction
2463 */
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002464 private final int nextSelectedPositionForDirection(
2465 View selectedView, int selectedPos, int direction) {
Adam Powell2a939112013-03-21 17:08:38 -07002466 int nextSelected;
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002467
Adam Powell2a939112013-03-21 17:08:38 -07002468 if (direction == View.FOCUS_DOWN) {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002469 final int listBottom = getHeight() - mListPadding.bottom;
2470 if (selectedView != null && selectedView.getBottom() <= listBottom) {
2471 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2472 selectedPos + 1 :
2473 mFirstPosition;
2474 } else {
2475 return INVALID_POSITION;
2476 }
Adam Powell2a939112013-03-21 17:08:38 -07002477 } else {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002478 final int listTop = mListPadding.top;
2479 if (selectedView != null && selectedView.getTop() >= listTop) {
2480 final int lastPos = mFirstPosition + getChildCount() - 1;
2481 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2482 selectedPos - 1 :
2483 lastPos;
2484 } else {
2485 return INVALID_POSITION;
2486 }
Adam Powell2a939112013-03-21 17:08:38 -07002487 }
2488
2489 if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2490 return INVALID_POSITION;
2491 }
2492 return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2493 }
2494
2495 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002496 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2497 * whether there are focusable items etc.
2498 *
2499 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2500 * @return Whether any scrolling, selection or focus change occured.
2501 */
2502 private boolean arrowScrollImpl(int direction) {
2503 if (getChildCount() <= 0) {
2504 return false;
2505 }
2506
2507 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002508 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002509
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002510 int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002511 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2512
2513 // if we are moving focus, we may OVERRIDE the default behavior
2514 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2515 if (focusResult != null) {
2516 nextSelectedPosition = focusResult.getSelectedPosition();
2517 amountToScroll = focusResult.getAmountToScroll();
2518 }
2519
2520 boolean needToRedraw = focusResult != null;
2521 if (nextSelectedPosition != INVALID_POSITION) {
2522 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2523 setSelectedPositionInt(nextSelectedPosition);
2524 setNextSelectedPositionInt(nextSelectedPosition);
2525 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002526 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002527 if (mItemsCanFocus && focusResult == null) {
2528 // there was no new view found to take focus, make sure we
2529 // don't leave focus with the old selection
2530 final View focused = getFocusedChild();
2531 if (focused != null) {
2532 focused.clearFocus();
2533 }
2534 }
2535 needToRedraw = true;
2536 checkSelectionChanged();
2537 }
2538
2539 if (amountToScroll > 0) {
2540 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2541 needToRedraw = true;
2542 }
2543
2544 // if we didn't find a new focusable, make sure any existing focused
2545 // item that was panned off screen gives up focus.
2546 if (mItemsCanFocus && (focusResult == null)
2547 && selectedView != null && selectedView.hasFocus()) {
2548 final View focused = selectedView.findFocus();
Mark Brophy1ea68892011-08-01 16:24:44 +01002549 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002550 focused.clearFocus();
2551 }
2552 }
2553
2554 // if the current selection is panned off, we need to remove the selection
2555 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2556 && !isViewAncestorOf(selectedView, this)) {
2557 selectedView = null;
2558 hideSelector();
2559
2560 // but we don't want to set the ressurect position (that would make subsequent
2561 // unhandled key events bring back the item we just scrolled off!)
2562 mResurrectToPosition = INVALID_POSITION;
2563 }
2564
2565 if (needToRedraw) {
2566 if (selectedView != null) {
Alan Viverettede399392014-05-01 17:20:55 -07002567 positionSelectorLikeFocus(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002568 mSelectedTop = selectedView.getTop();
2569 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002570 if (!awakenScrollBars()) {
2571 invalidate();
2572 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002573 invokeOnItemScrollListener();
2574 return true;
2575 }
2576
2577 return false;
2578 }
2579
2580 /**
2581 * When selection changes, it is possible that the previously selected or the
2582 * next selected item will change its size. If so, we need to offset some folks,
2583 * and re-layout the items as appropriate.
2584 *
2585 * @param selectedView The currently selected view (before changing selection).
2586 * should be <code>null</code> if there was no previous selection.
2587 * @param direction Either {@link android.view.View#FOCUS_UP} or
2588 * {@link android.view.View#FOCUS_DOWN}.
2589 * @param newSelectedPosition The position of the next selection.
2590 * @param newFocusAssigned whether new focus was assigned. This matters because
2591 * when something has focus, we don't want to show selection (ugh).
2592 */
2593 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2594 boolean newFocusAssigned) {
2595 if (newSelectedPosition == INVALID_POSITION) {
2596 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2597 }
2598
2599 // whether or not we are moving down or up, we want to preserve the
2600 // top of whatever view is on top:
2601 // - moving down: the view that had selection
2602 // - moving up: the view that is getting selection
2603 View topView;
2604 View bottomView;
2605 int topViewIndex, bottomViewIndex;
2606 boolean topSelected = false;
2607 final int selectedIndex = mSelectedPosition - mFirstPosition;
2608 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2609 if (direction == View.FOCUS_UP) {
2610 topViewIndex = nextSelectedIndex;
2611 bottomViewIndex = selectedIndex;
2612 topView = getChildAt(topViewIndex);
2613 bottomView = selectedView;
2614 topSelected = true;
2615 } else {
2616 topViewIndex = selectedIndex;
2617 bottomViewIndex = nextSelectedIndex;
2618 topView = selectedView;
2619 bottomView = getChildAt(bottomViewIndex);
2620 }
2621
2622 final int numChildren = getChildCount();
2623
2624 // start with top view: is it changing size?
2625 if (topView != null) {
2626 topView.setSelected(!newFocusAssigned && topSelected);
2627 measureAndAdjustDown(topView, topViewIndex, numChildren);
2628 }
2629
2630 // is the bottom view changing size?
2631 if (bottomView != null) {
2632 bottomView.setSelected(!newFocusAssigned && !topSelected);
2633 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2634 }
2635 }
2636
2637 /**
2638 * Re-measure a child, and if its height changes, lay it out preserving its
2639 * top, and adjust the children below it appropriately.
2640 * @param child The child
2641 * @param childIndex The view group index of the child.
2642 * @param numChildren The number of children in the view group.
2643 */
2644 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2645 int oldHeight = child.getHeight();
2646 measureItem(child);
2647 if (child.getMeasuredHeight() != oldHeight) {
2648 // lay out the view, preserving its top
2649 relayoutMeasuredItem(child);
2650
2651 // adjust views below appropriately
2652 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2653 for (int i = childIndex + 1; i < numChildren; i++) {
2654 getChildAt(i).offsetTopAndBottom(heightDelta);
2655 }
2656 }
2657 }
2658
2659 /**
2660 * Measure a particular list child.
2661 * TODO: unify with setUpChild.
2662 * @param child The child.
2663 */
2664 private void measureItem(View child) {
2665 ViewGroup.LayoutParams p = child.getLayoutParams();
2666 if (p == null) {
2667 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002668 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002669 ViewGroup.LayoutParams.WRAP_CONTENT);
2670 }
2671
2672 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2673 mListPadding.left + mListPadding.right, p.width);
2674 int lpHeight = p.height;
2675 int childHeightSpec;
2676 if (lpHeight > 0) {
2677 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2678 } else {
2679 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2680 }
2681 child.measure(childWidthSpec, childHeightSpec);
2682 }
2683
2684 /**
2685 * Layout a child that has been measured, preserving its top position.
2686 * TODO: unify with setUpChild.
2687 * @param child The child.
2688 */
2689 private void relayoutMeasuredItem(View child) {
2690 final int w = child.getMeasuredWidth();
2691 final int h = child.getMeasuredHeight();
2692 final int childLeft = mListPadding.left;
2693 final int childRight = childLeft + w;
2694 final int childTop = child.getTop();
2695 final int childBottom = childTop + h;
2696 child.layout(childLeft, childTop, childRight, childBottom);
2697 }
2698
2699 /**
2700 * @return The amount to preview next items when arrow srolling.
2701 */
2702 private int getArrowScrollPreviewLength() {
2703 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2704 }
2705
2706 /**
2707 * Determine how much we need to scroll in order to get the next selected view
2708 * visible, with a fading edge showing below as applicable. The amount is
2709 * capped at {@link #getMaxScrollAmount()} .
2710 *
2711 * @param direction either {@link android.view.View#FOCUS_UP} or
2712 * {@link android.view.View#FOCUS_DOWN}.
2713 * @param nextSelectedPosition The position of the next selection, or
2714 * {@link #INVALID_POSITION} if there is no next selectable position
2715 * @return The amount to scroll. Note: this is always positive! Direction
2716 * needs to be taken into account when actually scrolling.
2717 */
2718 private int amountToScroll(int direction, int nextSelectedPosition) {
2719 final int listBottom = getHeight() - mListPadding.bottom;
2720 final int listTop = mListPadding.top;
2721
Justin Ho1ad11b92013-02-25 16:09:20 -08002722 int numChildren = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002723
2724 if (direction == View.FOCUS_DOWN) {
2725 int indexToMakeVisible = numChildren - 1;
2726 if (nextSelectedPosition != INVALID_POSITION) {
2727 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2728 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002729 while (numChildren <= indexToMakeVisible) {
2730 // Child to view is not attached yet.
2731 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2732 numChildren++;
2733 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002734 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2735 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2736
2737 int goalBottom = listBottom;
2738 if (positionToMakeVisible < mItemCount - 1) {
2739 goalBottom -= getArrowScrollPreviewLength();
2740 }
2741
2742 if (viewToMakeVisible.getBottom() <= goalBottom) {
2743 // item is fully visible.
2744 return 0;
2745 }
2746
2747 if (nextSelectedPosition != INVALID_POSITION
2748 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2749 // item already has enough of it visible, changing selection is good enough
2750 return 0;
2751 }
2752
2753 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2754
2755 if ((mFirstPosition + numChildren) == mItemCount) {
2756 // last is last in list -> make sure we don't scroll past it
2757 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2758 amountToScroll = Math.min(amountToScroll, max);
2759 }
2760
2761 return Math.min(amountToScroll, getMaxScrollAmount());
2762 } else {
2763 int indexToMakeVisible = 0;
2764 if (nextSelectedPosition != INVALID_POSITION) {
2765 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2766 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002767 while (indexToMakeVisible < 0) {
2768 // Child to view is not attached yet.
2769 addViewAbove(getChildAt(0), mFirstPosition);
2770 mFirstPosition--;
2771 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2772 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002773 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2774 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2775 int goalTop = listTop;
2776 if (positionToMakeVisible > 0) {
2777 goalTop += getArrowScrollPreviewLength();
2778 }
2779 if (viewToMakeVisible.getTop() >= goalTop) {
2780 // item is fully visible.
2781 return 0;
2782 }
2783
2784 if (nextSelectedPosition != INVALID_POSITION &&
2785 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2786 // item already has enough of it visible, changing selection is good enough
2787 return 0;
2788 }
2789
2790 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2791 if (mFirstPosition == 0) {
2792 // first is first in list -> make sure we don't scroll past it
2793 final int max = listTop - getChildAt(0).getTop();
2794 amountToScroll = Math.min(amountToScroll, max);
2795 }
2796 return Math.min(amountToScroll, getMaxScrollAmount());
2797 }
2798 }
2799
2800 /**
2801 * Holds results of focus aware arrow scrolling.
2802 */
2803 static private class ArrowScrollFocusResult {
2804 private int mSelectedPosition;
2805 private int mAmountToScroll;
2806
2807 /**
2808 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2809 */
2810 void populate(int selectedPosition, int amountToScroll) {
2811 mSelectedPosition = selectedPosition;
2812 mAmountToScroll = amountToScroll;
2813 }
2814
2815 public int getSelectedPosition() {
2816 return mSelectedPosition;
2817 }
2818
2819 public int getAmountToScroll() {
2820 return mAmountToScroll;
2821 }
2822 }
2823
2824 /**
2825 * @param direction either {@link android.view.View#FOCUS_UP} or
2826 * {@link android.view.View#FOCUS_DOWN}.
2827 * @return The position of the next selectable position of the views that
2828 * are currently visible, taking into account the fact that there might
2829 * be no selection. Returns {@link #INVALID_POSITION} if there is no
2830 * selectable view on screen in the given direction.
2831 */
2832 private int lookForSelectablePositionOnScreen(int direction) {
2833 final int firstPosition = mFirstPosition;
2834 if (direction == View.FOCUS_DOWN) {
2835 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2836 mSelectedPosition + 1 :
2837 firstPosition;
2838 if (startPos >= mAdapter.getCount()) {
2839 return INVALID_POSITION;
2840 }
2841 if (startPos < firstPosition) {
2842 startPos = firstPosition;
2843 }
2844
2845 final int lastVisiblePos = getLastVisiblePosition();
2846 final ListAdapter adapter = getAdapter();
2847 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2848 if (adapter.isEnabled(pos)
2849 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2850 return pos;
2851 }
2852 }
2853 } else {
2854 int last = firstPosition + getChildCount() - 1;
2855 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2856 mSelectedPosition - 1 :
2857 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08002858 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002859 return INVALID_POSITION;
2860 }
2861 if (startPos > last) {
2862 startPos = last;
2863 }
2864
2865 final ListAdapter adapter = getAdapter();
2866 for (int pos = startPos; pos >= firstPosition; pos--) {
2867 if (adapter.isEnabled(pos)
2868 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2869 return pos;
2870 }
2871 }
2872 }
2873 return INVALID_POSITION;
2874 }
2875
2876 /**
2877 * Do an arrow scroll based on focus searching. If a new view is
2878 * given focus, return the selection delta and amount to scroll via
2879 * an {@link ArrowScrollFocusResult}, otherwise, return null.
2880 *
2881 * @param direction either {@link android.view.View#FOCUS_UP} or
2882 * {@link android.view.View#FOCUS_DOWN}.
2883 * @return The result if focus has changed, or <code>null</code>.
2884 */
2885 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2886 final View selectedView = getSelectedView();
2887 View newFocus;
2888 if (selectedView != null && selectedView.hasFocus()) {
2889 View oldFocus = selectedView.findFocus();
2890 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2891 } else {
2892 if (direction == View.FOCUS_DOWN) {
2893 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2894 final int listTop = mListPadding.top +
2895 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2896 final int ySearchPoint =
2897 (selectedView != null && selectedView.getTop() > listTop) ?
2898 selectedView.getTop() :
2899 listTop;
2900 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2901 } else {
2902 final boolean bottomFadingEdgeShowing =
2903 (mFirstPosition + getChildCount() - 1) < mItemCount;
2904 final int listBottom = getHeight() - mListPadding.bottom -
2905 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2906 final int ySearchPoint =
2907 (selectedView != null && selectedView.getBottom() < listBottom) ?
2908 selectedView.getBottom() :
2909 listBottom;
2910 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2911 }
2912 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2913 }
2914
2915 if (newFocus != null) {
2916 final int positionOfNewFocus = positionOfNewFocus(newFocus);
2917
2918 // if the focus change is in a different new position, make sure
2919 // we aren't jumping over another selectable position
2920 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2921 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2922 if (selectablePosition != INVALID_POSITION &&
2923 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2924 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2925 return null;
2926 }
2927 }
2928
2929 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2930
2931 final int maxScrollAmount = getMaxScrollAmount();
2932 if (focusScroll < maxScrollAmount) {
2933 // not moving too far, safe to give next view focus
2934 newFocus.requestFocus(direction);
2935 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2936 return mArrowScrollFocusResult;
2937 } else if (distanceToView(newFocus) < maxScrollAmount){
2938 // Case to consider:
2939 // too far to get entire next focusable on screen, but by going
2940 // max scroll amount, we are getting it at least partially in view,
2941 // so give it focus and scroll the max ammount.
2942 newFocus.requestFocus(direction);
2943 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2944 return mArrowScrollFocusResult;
2945 }
2946 }
2947 return null;
2948 }
2949
2950 /**
2951 * @param newFocus The view that would have focus.
2952 * @return the position that contains newFocus
2953 */
2954 private int positionOfNewFocus(View newFocus) {
2955 final int numChildren = getChildCount();
2956 for (int i = 0; i < numChildren; i++) {
2957 final View child = getChildAt(i);
2958 if (isViewAncestorOf(newFocus, child)) {
2959 return mFirstPosition + i;
2960 }
2961 }
2962 throw new IllegalArgumentException("newFocus is not a child of any of the"
2963 + " children of the list!");
2964 }
2965
2966 /**
2967 * Return true if child is an ancestor of parent, (or equal to the parent).
2968 */
2969 private boolean isViewAncestorOf(View child, View parent) {
2970 if (child == parent) {
2971 return true;
2972 }
2973
2974 final ViewParent theParent = child.getParent();
2975 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2976 }
2977
2978 /**
2979 * Determine how much we need to scroll in order to get newFocus in view.
2980 * @param direction either {@link android.view.View#FOCUS_UP} or
2981 * {@link android.view.View#FOCUS_DOWN}.
2982 * @param newFocus The view that would take focus.
2983 * @param positionOfNewFocus The position of the list item containing newFocus
2984 * @return The amount to scroll. Note: this is always positive! Direction
2985 * needs to be taken into account when actually scrolling.
2986 */
2987 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2988 int amountToScroll = 0;
2989 newFocus.getDrawingRect(mTempRect);
2990 offsetDescendantRectToMyCoords(newFocus, mTempRect);
2991 if (direction == View.FOCUS_UP) {
2992 if (mTempRect.top < mListPadding.top) {
2993 amountToScroll = mListPadding.top - mTempRect.top;
2994 if (positionOfNewFocus > 0) {
2995 amountToScroll += getArrowScrollPreviewLength();
2996 }
2997 }
2998 } else {
2999 final int listBottom = getHeight() - mListPadding.bottom;
3000 if (mTempRect.bottom > listBottom) {
3001 amountToScroll = mTempRect.bottom - listBottom;
3002 if (positionOfNewFocus < mItemCount - 1) {
3003 amountToScroll += getArrowScrollPreviewLength();
3004 }
3005 }
3006 }
3007 return amountToScroll;
3008 }
3009
3010 /**
3011 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003012 * direction.
3013 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003014 * @param descendant A descendant of this list.
3015 * @return The distance, or 0 if the nearest edge is already on screen.
3016 */
3017 private int distanceToView(View descendant) {
3018 int distance = 0;
3019 descendant.getDrawingRect(mTempRect);
3020 offsetDescendantRectToMyCoords(descendant, mTempRect);
3021 final int listBottom = mBottom - mTop - mListPadding.bottom;
3022 if (mTempRect.bottom < mListPadding.top) {
3023 distance = mListPadding.top - mTempRect.bottom;
3024 } else if (mTempRect.top > listBottom) {
3025 distance = mTempRect.top - listBottom;
3026 }
3027 return distance;
3028 }
3029
3030
3031 /**
3032 * Scroll the children by amount, adding a view at the end and removing
3033 * views that fall off as necessary.
3034 *
3035 * @param amount The amount (positive or negative) to scroll.
3036 */
3037 private void scrollListItemsBy(int amount) {
3038 offsetChildrenTopAndBottom(amount);
3039
3040 final int listBottom = getHeight() - mListPadding.bottom;
3041 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003042 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003043
3044 if (amount < 0) {
3045 // shifted items up
3046
3047 // may need to pan views into the bottom space
3048 int numChildren = getChildCount();
3049 View last = getChildAt(numChildren - 1);
3050 while (last.getBottom() < listBottom) {
3051 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3052 if (lastVisiblePosition < mItemCount - 1) {
3053 last = addViewBelow(last, lastVisiblePosition);
3054 numChildren++;
3055 } else {
3056 break;
3057 }
3058 }
3059
3060 // may have brought in the last child of the list that is skinnier
3061 // than the fading edge, thereby leaving space at the end. need
3062 // to shift back
3063 if (last.getBottom() < listBottom) {
3064 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3065 }
3066
3067 // top views may be panned off screen
3068 View first = getChildAt(0);
3069 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003070 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3071 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003072 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003073 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003074 detachViewFromParent(first);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003075 first = getChildAt(0);
3076 mFirstPosition++;
3077 }
3078 } else {
3079 // shifted items down
3080 View first = getChildAt(0);
3081
3082 // may need to pan views into top
3083 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3084 first = addViewAbove(first, mFirstPosition);
3085 mFirstPosition--;
3086 }
3087
3088 // may have brought the very first child of the list in too far and
3089 // need to shift it back
3090 if (first.getTop() > listTop) {
3091 offsetChildrenTopAndBottom(listTop - first.getTop());
3092 }
3093
3094 int lastIndex = getChildCount() - 1;
3095 View last = getChildAt(lastIndex);
3096
3097 // bottom view may be panned off screen
3098 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003099 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3100 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003101 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003102 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003103 detachViewFromParent(last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003104 last = getChildAt(--lastIndex);
3105 }
3106 }
3107 }
3108
3109 private View addViewAbove(View theView, int position) {
3110 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08003111 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003112 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003113 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3114 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003115 return view;
3116 }
3117
3118 private View addViewBelow(View theView, int position) {
3119 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08003120 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003121 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003122 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3123 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003124 return view;
3125 }
3126
3127 /**
3128 * Indicates that the views created by the ListAdapter can contain focusable
3129 * items.
3130 *
3131 * @param itemsCanFocus true if items can get focus, false otherwise
3132 */
3133 public void setItemsCanFocus(boolean itemsCanFocus) {
3134 mItemsCanFocus = itemsCanFocus;
3135 if (!itemsCanFocus) {
3136 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3137 }
3138 }
3139
3140 /**
3141 * @return Whether the views created by the ListAdapter can contain focusable
3142 * items.
3143 */
3144 public boolean getItemsCanFocus() {
3145 return mItemsCanFocus;
3146 }
3147
3148 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003149 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003150 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f2009-05-15 16:03:59 -07003151 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003152 if (retValue) {
3153 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d2011-03-07 15:36:33 -08003154 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003155 View first = getChildAt(0);
3156 if (first == null || first.getTop() > listTop) {
3157 return false;
3158 }
Adam Powell3ba8f5d2011-03-07 15:36:33 -08003159 final int listBottom = getHeight() -
3160 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003161 View last = getChildAt(getChildCount() - 1);
3162 if (last == null || last.getBottom() < listBottom) {
3163 return false;
3164 }
3165 }
3166 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003167 }
3168
3169 @Override
3170 public void setCacheColorHint(int color) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003171 final boolean opaque = (color >>> 24) == 0xFF;
3172 mIsCacheColorOpaque = opaque;
3173 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003174 if (mDividerPaint == null) {
3175 mDividerPaint = new Paint();
3176 }
Romain Guy8f1344f2009-05-15 16:03:59 -07003177 mDividerPaint.setColor(color);
3178 }
Romain Guy24443ea2009-05-11 11:56:30 -07003179 super.setCacheColorHint(color);
3180 }
Adam Powell637d3372010-08-25 14:37:03 -07003181
3182 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3183 final int height = drawable.getMinimumHeight();
3184
3185 canvas.save();
3186 canvas.clipRect(bounds);
3187
3188 final int span = bounds.bottom - bounds.top;
3189 if (span < height) {
3190 bounds.top = bounds.bottom - height;
3191 }
3192
3193 drawable.setBounds(bounds);
3194 drawable.draw(canvas);
3195
3196 canvas.restore();
3197 }
3198
3199 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3200 final int height = drawable.getMinimumHeight();
3201
3202 canvas.save();
3203 canvas.clipRect(bounds);
3204
3205 final int span = bounds.bottom - bounds.top;
3206 if (span < height) {
3207 bounds.bottom = bounds.top + height;
3208 }
3209
3210 drawable.setBounds(bounds);
3211 drawable.draw(canvas);
3212
3213 canvas.restore();
3214 }
3215
Romain Guy24443ea2009-05-11 11:56:30 -07003216 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003217 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003218 if (mCachingStarted) {
3219 mCachingActive = true;
3220 }
3221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003222 // Draw the dividers
3223 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003224 final Drawable overscrollHeader = mOverScrollHeader;
3225 final Drawable overscrollFooter = mOverScrollFooter;
3226 final boolean drawOverscrollHeader = overscrollHeader != null;
3227 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003228 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003229
Adam Powell637d3372010-08-25 14:37:03 -07003230 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003231 // Only modify the top and bottom in the loop, we set the left and right here
3232 final Rect bounds = mTempRect;
3233 bounds.left = mPaddingLeft;
3234 bounds.right = mRight - mLeft - mPaddingRight;
3235
3236 final int count = getChildCount();
3237 final int headerCount = mHeaderViewInfos.size();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003238 final int itemCount = mItemCount;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003239 final int footerLimit = (itemCount - mFooterViewInfos.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003240 final boolean headerDividers = mHeaderDividersEnabled;
3241 final boolean footerDividers = mFooterDividersEnabled;
3242 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003243 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3244 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003245 // If the list is opaque *and* the background is not, we want to
3246 // fill a rect where the dividers would be for non-selectable items
3247 // If the list is opaque and the background is also opaque, we don't
3248 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003249 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003250
3251 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003252 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003253 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003254 }
Romain Guy8f1344f2009-05-15 16:03:59 -07003255 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003256
Adam Powell94566552011-01-05 23:25:33 -08003257 int effectivePaddingTop = 0;
3258 int effectivePaddingBottom = 0;
3259 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3260 effectivePaddingTop = mListPadding.top;
3261 effectivePaddingBottom = mListPadding.bottom;
3262 }
3263
3264 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003265 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003266 int bottom = 0;
Adam Powell0b8bb422010-02-08 14:30:45 -08003267
Adam Powell637d3372010-08-25 14:37:03 -07003268 // Draw top divider or header for overscroll
3269 final int scrollY = mScrollY;
3270 if (count > 0 && scrollY < 0) {
3271 if (drawOverscrollHeader) {
3272 bounds.bottom = 0;
3273 bounds.top = scrollY;
3274 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3275 } else if (drawDividers) {
3276 bounds.bottom = 0;
3277 bounds.top = -dividerHeight;
3278 drawDivider(canvas, bounds, -1);
3279 }
3280 }
3281
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003282 for (int i = 0; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003283 final int itemIndex = (first + i);
3284 final boolean isHeader = (itemIndex < headerCount);
3285 final boolean isFooter = (itemIndex >= footerLimit);
3286 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3287 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003288 bottom = child.getBottom();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003289 final boolean isLastItem = (i == (count - 1));
Adam Powell637d3372010-08-25 14:37:03 -07003290
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003291 if (drawDividers && (bottom < listBottom)
3292 && !(drawOverscrollFooter && isLastItem)) {
3293 final int nextIndex = (itemIndex + 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003294 // Draw dividers between enabled items, headers
3295 // and/or footers when enabled and requested, and
3296 // after the last enabled item.
3297 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3298 && (nextIndex >= headerCount)) && (isLastItem
3299 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3300 && (nextIndex < footerLimit)))) {
Adam Powell637d3372010-08-25 14:37:03 -07003301 bounds.top = bottom;
3302 bounds.bottom = bottom + dividerHeight;
3303 drawDivider(canvas, bounds, i);
3304 } else if (fillForMissingDividers) {
3305 bounds.top = bottom;
3306 bounds.bottom = bottom + dividerHeight;
3307 canvas.drawRect(bounds, paint);
3308 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003309 }
3310 }
3311 }
Adam Powell637d3372010-08-25 14:37:03 -07003312
3313 final int overFooterBottom = mBottom + mScrollY;
3314 if (drawOverscrollFooter && first + count == itemCount &&
3315 overFooterBottom > bottom) {
3316 bounds.top = bottom;
3317 bounds.bottom = overFooterBottom;
3318 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3319 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003320 } else {
3321 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003322
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003323 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003324
Adam Powell637d3372010-08-25 14:37:03 -07003325 if (count > 0 && drawOverscrollHeader) {
3326 bounds.top = scrollY;
3327 bounds.bottom = getChildAt(0).getTop();
3328 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3329 }
3330
3331 final int start = drawOverscrollHeader ? 1 : 0;
3332 for (int i = start; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003333 final int itemIndex = (first + i);
3334 final boolean isHeader = (itemIndex < headerCount);
3335 final boolean isFooter = (itemIndex >= footerLimit);
3336 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3337 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003338 top = child.getTop();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003339 if (drawDividers && (top > effectivePaddingTop)) {
3340 final boolean isFirstItem = (i == start);
3341 final int previousIndex = (itemIndex - 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003342 // Draw dividers between enabled items, headers
3343 // and/or footers when enabled and requested, and
3344 // before the first enabled item.
3345 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3346 && (previousIndex >= headerCount)) && (isFirstItem ||
3347 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3348 && (previousIndex < footerLimit)))) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003349 bounds.top = top - dividerHeight;
3350 bounds.bottom = top;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003351 // Give the method the child ABOVE the divider,
3352 // so we subtract one from our child position.
3353 // Give -1 when there is no child above the
Romain Guy8f1344f2009-05-15 16:03:59 -07003354 // divider.
3355 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003356 } else if (fillForMissingDividers) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003357 bounds.top = top - dividerHeight;
3358 bounds.bottom = top;
3359 canvas.drawRect(bounds, paint);
3360 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003361 }
3362 }
3363 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003364
Romain Guy179de8a2010-07-09 13:27:00 -07003365 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003366 if (drawOverscrollFooter) {
3367 final int absListBottom = mBottom;
3368 bounds.top = absListBottom;
3369 bounds.bottom = absListBottom + scrollY;
3370 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3371 } else if (drawDividers) {
3372 bounds.top = listBottom;
3373 bounds.bottom = listBottom + dividerHeight;
3374 drawDivider(canvas, bounds, -1);
3375 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003376 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003377 }
3378 }
3379
3380 // Draw the indicators (these should be drawn above the dividers) and children
3381 super.dispatchDraw(canvas);
3382 }
3383
Romain Guy0211a0a2011-02-14 16:34:59 -08003384 @Override
3385 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3386 boolean more = super.drawChild(canvas, child, drawingTime);
3387 if (mCachingActive && child.mCachingFailed) {
3388 mCachingActive = false;
3389 }
3390 return more;
3391 }
3392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003393 /**
3394 * Draws a divider for the given child in the given bounds.
3395 *
3396 * @param canvas The canvas to draw to.
3397 * @param bounds The bounds of the divider.
3398 * @param childIndex The index of child (of the View) above the divider.
3399 * This will be -1 if there is no child above the divider to be
3400 * drawn.
3401 */
3402 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3403 // This widget draws the same divider for all children
3404 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003405
Romain Guy95930e12010-10-04 13:46:02 -07003406 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003407 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003408 }
3409
3410 /**
3411 * Returns the drawable that will be drawn between each item in the list.
3412 *
3413 * @return the current drawable drawn between list elements
3414 */
3415 public Drawable getDivider() {
3416 return mDivider;
3417 }
3418
3419 /**
3420 * Sets the drawable that will be drawn between each item in the list. If the drawable does
3421 * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3422 *
3423 * @param divider The drawable to use.
3424 */
3425 public void setDivider(Drawable divider) {
3426 if (divider != null) {
3427 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003428 } else {
3429 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003430 }
3431 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003432 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003433 requestLayout();
3434 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003435 }
3436
3437 /**
3438 * @return Returns the height of the divider that will be drawn between each item in the list.
3439 */
3440 public int getDividerHeight() {
3441 return mDividerHeight;
3442 }
3443
3444 /**
3445 * Sets the height of the divider that will be drawn between each item in the list. Calling
3446 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3447 *
3448 * @param height The new height of the divider in pixels.
3449 */
3450 public void setDividerHeight(int height) {
3451 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003452 requestLayout();
3453 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003454 }
3455
3456 /**
3457 * Enables or disables the drawing of the divider for header views.
3458 *
3459 * @param headerDividersEnabled True to draw the headers, false otherwise.
3460 *
3461 * @see #setFooterDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003462 * @see #areHeaderDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003463 * @see #addHeaderView(android.view.View)
3464 */
3465 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3466 mHeaderDividersEnabled = headerDividersEnabled;
3467 invalidate();
3468 }
3469
3470 /**
Alan Viverette55421032013-06-11 17:50:56 -07003471 * @return Whether the drawing of the divider for header views is enabled
3472 *
3473 * @see #setHeaderDividersEnabled(boolean)
3474 */
3475 public boolean areHeaderDividersEnabled() {
3476 return mHeaderDividersEnabled;
3477 }
3478
3479 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003480 * Enables or disables the drawing of the divider for footer views.
3481 *
3482 * @param footerDividersEnabled True to draw the footers, false otherwise.
3483 *
3484 * @see #setHeaderDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003485 * @see #areFooterDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003486 * @see #addFooterView(android.view.View)
3487 */
3488 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3489 mFooterDividersEnabled = footerDividersEnabled;
3490 invalidate();
3491 }
Alan Viverette55421032013-06-11 17:50:56 -07003492
3493 /**
3494 * @return Whether the drawing of the divider for footer views is enabled
3495 *
3496 * @see #setFooterDividersEnabled(boolean)
3497 */
3498 public boolean areFooterDividersEnabled() {
3499 return mFooterDividersEnabled;
3500 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003501
Adam Powell637d3372010-08-25 14:37:03 -07003502 /**
3503 * Sets the drawable that will be drawn above all other list content.
3504 * This area can become visible when the user overscrolls the list.
3505 *
3506 * @param header The drawable to use
3507 */
3508 public void setOverscrollHeader(Drawable header) {
3509 mOverScrollHeader = header;
3510 if (mScrollY < 0) {
3511 invalidate();
3512 }
3513 }
3514
3515 /**
3516 * @return The drawable that will be drawn above all other list content
3517 */
3518 public Drawable getOverscrollHeader() {
3519 return mOverScrollHeader;
3520 }
3521
3522 /**
3523 * Sets the drawable that will be drawn below all other list content.
3524 * This area can become visible when the user overscrolls the list,
3525 * or when the list's content does not fully fill the container area.
3526 *
3527 * @param footer The drawable to use
3528 */
3529 public void setOverscrollFooter(Drawable footer) {
3530 mOverScrollFooter = footer;
3531 invalidate();
3532 }
3533
3534 /**
3535 * @return The drawable that will be drawn below all other list content
3536 */
3537 public Drawable getOverscrollFooter() {
3538 return mOverScrollFooter;
3539 }
3540
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003541 @Override
3542 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3543 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3544
Adam Powelle1bf4862011-09-02 16:56:20 -07003545 final ListAdapter adapter = mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003546 int closetChildIndex = -1;
Adam Powelldcce1212011-10-31 16:41:21 -07003547 int closestChildTop = 0;
Adam Powelle1bf4862011-09-02 16:56:20 -07003548 if (adapter != null && gainFocus && previouslyFocusedRect != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003549 previouslyFocusedRect.offset(mScrollX, mScrollY);
3550
Adam Powelld7507832010-02-18 15:40:33 -08003551 // Don't cache the result of getChildCount or mFirstPosition here,
3552 // it could change in layoutChildren.
3553 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003554 mLayoutMode = LAYOUT_NORMAL;
3555 layoutChildren();
3556 }
3557
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003558 // figure out which item should be selected based on previously
3559 // focused rect
3560 Rect otherRect = mTempRect;
3561 int minDistance = Integer.MAX_VALUE;
3562 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003563 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003564
3565 for (int i = 0; i < childCount; i++) {
3566 // only consider selectable views
3567 if (!adapter.isEnabled(firstPosition + i)) {
3568 continue;
3569 }
3570
3571 View other = getChildAt(i);
3572 other.getDrawingRect(otherRect);
3573 offsetDescendantRectToMyCoords(other, otherRect);
3574 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3575
3576 if (distance < minDistance) {
3577 minDistance = distance;
3578 closetChildIndex = i;
Adam Powelldcce1212011-10-31 16:41:21 -07003579 closestChildTop = other.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003580 }
3581 }
3582 }
3583
3584 if (closetChildIndex >= 0) {
Adam Powelldcce1212011-10-31 16:41:21 -07003585 setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003586 } else {
3587 requestLayout();
3588 }
3589 }
3590
3591
3592 /*
3593 * (non-Javadoc)
3594 *
3595 * Children specified in XML are assumed to be header views. After we have
3596 * parsed them move them out of the children list and into mHeaderViews.
3597 */
3598 @Override
3599 protected void onFinishInflate() {
3600 super.onFinishInflate();
3601
3602 int count = getChildCount();
3603 if (count > 0) {
3604 for (int i = 0; i < count; ++i) {
3605 addHeaderView(getChildAt(i));
3606 }
3607 removeAllViews();
3608 }
3609 }
3610
3611 /* (non-Javadoc)
3612 * @see android.view.View#findViewById(int)
3613 * First look in our children, then in any header and footer views that may be scrolled off.
3614 */
3615 @Override
3616 protected View findViewTraversal(int id) {
3617 View v;
3618 v = super.findViewTraversal(id);
3619 if (v == null) {
3620 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3621 if (v != null) {
3622 return v;
3623 }
3624 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3625 if (v != null) {
3626 return v;
3627 }
3628 }
3629 return v;
3630 }
3631
3632 /* (non-Javadoc)
3633 *
3634 * Look in the passed in list of headers or footers for the view.
3635 */
3636 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3637 if (where != null) {
3638 int len = where.size();
3639 View v;
3640
3641 for (int i = 0; i < len; i++) {
3642 v = where.get(i).view;
3643
3644 if (!v.isRootNamespace()) {
3645 v = v.findViewById(id);
3646
3647 if (v != null) {
3648 return v;
3649 }
3650 }
3651 }
3652 }
3653 return null;
3654 }
3655
3656 /* (non-Javadoc)
Jeff Brown4e6319b2010-12-13 10:36:51 -08003657 * @see android.view.View#findViewWithTag(Object)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003658 * First look in our children, then in any header and footer views that may be scrolled off.
3659 */
3660 @Override
3661 protected View findViewWithTagTraversal(Object tag) {
3662 View v;
3663 v = super.findViewWithTagTraversal(tag);
3664 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003665 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003666 if (v != null) {
3667 return v;
3668 }
3669
Jeff Brown4e6319b2010-12-13 10:36:51 -08003670 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003671 if (v != null) {
3672 return v;
3673 }
3674 }
3675 return v;
3676 }
3677
3678 /* (non-Javadoc)
3679 *
3680 * Look in the passed in list of headers or footers for the view with the tag.
3681 */
Jeff Brown4e6319b2010-12-13 10:36:51 -08003682 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003683 if (where != null) {
3684 int len = where.size();
3685 View v;
3686
3687 for (int i = 0; i < len; i++) {
3688 v = where.get(i).view;
3689
3690 if (!v.isRootNamespace()) {
3691 v = v.findViewWithTag(tag);
3692
3693 if (v != null) {
3694 return v;
3695 }
3696 }
3697 }
3698 }
3699 return null;
3700 }
3701
Jeff Brown4e6319b2010-12-13 10:36:51 -08003702 /**
3703 * @hide
3704 * @see android.view.View#findViewByPredicate(Predicate)
3705 * First look in our children, then in any header and footer views that may be scrolled off.
3706 */
3707 @Override
Jeff Brown4dfbec22011-08-15 14:55:37 -07003708 protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003709 View v;
Jeff Brown4dfbec22011-08-15 14:55:37 -07003710 v = super.findViewByPredicateTraversal(predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003711 if (v == null) {
Jeff Brown4dfbec22011-08-15 14:55:37 -07003712 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003713 if (v != null) {
3714 return v;
3715 }
3716
Jeff Brown4dfbec22011-08-15 14:55:37 -07003717 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003718 if (v != null) {
3719 return v;
3720 }
3721 }
3722 return v;
3723 }
3724
3725 /* (non-Javadoc)
3726 *
3727 * Look in the passed in list of headers or footers for the first view that matches
3728 * the predicate.
3729 */
3730 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
Jeff Brown4dfbec22011-08-15 14:55:37 -07003731 Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003732 if (where != null) {
3733 int len = where.size();
3734 View v;
3735
3736 for (int i = 0; i < len; i++) {
3737 v = where.get(i).view;
3738
Jeff Brown4dfbec22011-08-15 14:55:37 -07003739 if (v != childToSkip && !v.isRootNamespace()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003740 v = v.findViewByPredicate(predicate);
3741
3742 if (v != null) {
3743 return v;
3744 }
3745 }
3746 }
3747 }
3748 return null;
3749 }
3750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003751 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003752 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003753 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3754 *
3755 * @return A new array which contains the id of each checked item in the
3756 * list.
3757 *
Adam Powell463ceff2010-03-09 11:50:51 -08003758 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003759 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003760 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003761 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003762 // Use new behavior that correctly handles stable ID mapping.
3763 if (mAdapter != null && mAdapter.hasStableIds()) {
3764 return getCheckedItemIds();
3765 }
3766
3767 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3768 // Fall back to it to support legacy apps.
3769 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3770 final SparseBooleanArray states = mCheckStates;
3771 final int count = states.size();
3772 final long[] ids = new long[count];
3773 final ListAdapter adapter = mAdapter;
3774
3775 int checkedCount = 0;
3776 for (int i = 0; i < count; i++) {
3777 if (states.valueAt(i)) {
3778 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3779 }
3780 }
3781
3782 // Trim array if needed. mCheckStates may contain false values
3783 // resulting in checkedCount being smaller than count.
3784 if (checkedCount == count) {
3785 return ids;
3786 } else {
3787 final long[] result = new long[checkedCount];
3788 System.arraycopy(ids, 0, result, 0, checkedCount);
3789
3790 return result;
3791 }
3792 }
3793 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08003794 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003795
3796 @Override
Alan Viverette441b4372014-02-12 13:30:20 -08003797 int getHeightForPosition(int position) {
3798 final int height = super.getHeightForPosition(position);
3799 if (shouldAdjustHeightForDivider(position)) {
3800 return height + mDividerHeight;
3801 }
3802 return height;
3803 }
3804
3805 private boolean shouldAdjustHeightForDivider(int itemIndex) {
3806 final int dividerHeight = mDividerHeight;
3807 final Drawable overscrollHeader = mOverScrollHeader;
3808 final Drawable overscrollFooter = mOverScrollFooter;
3809 final boolean drawOverscrollHeader = overscrollHeader != null;
3810 final boolean drawOverscrollFooter = overscrollFooter != null;
3811 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3812
3813 if (drawDividers) {
3814 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3815 final int itemCount = mItemCount;
3816 final int headerCount = mHeaderViewInfos.size();
3817 final int footerLimit = (itemCount - mFooterViewInfos.size());
3818 final boolean isHeader = (itemIndex < headerCount);
3819 final boolean isFooter = (itemIndex >= footerLimit);
3820 final boolean headerDividers = mHeaderDividersEnabled;
3821 final boolean footerDividers = mFooterDividersEnabled;
3822 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3823 final ListAdapter adapter = mAdapter;
3824 if (!mStackFromBottom) {
3825 final boolean isLastItem = (itemIndex == (itemCount - 1));
3826 if (!drawOverscrollFooter || !isLastItem) {
3827 final int nextIndex = itemIndex + 1;
3828 // Draw dividers between enabled items, headers
3829 // and/or footers when enabled and requested, and
3830 // after the last enabled item.
3831 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3832 && (nextIndex >= headerCount)) && (isLastItem
3833 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3834 && (nextIndex < footerLimit)))) {
3835 return true;
3836 } else if (fillForMissingDividers) {
3837 return true;
3838 }
3839 }
3840 } else {
3841 final int start = drawOverscrollHeader ? 1 : 0;
3842 final boolean isFirstItem = (itemIndex == start);
3843 if (!isFirstItem) {
3844 final int previousIndex = (itemIndex - 1);
3845 // Draw dividers between enabled items, headers
3846 // and/or footers when enabled and requested, and
3847 // before the first enabled item.
3848 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3849 && (previousIndex >= headerCount)) && (isFirstItem ||
3850 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3851 && (previousIndex < footerLimit)))) {
3852 return true;
3853 } else if (fillForMissingDividers) {
3854 return true;
3855 }
3856 }
3857 }
3858 }
3859 }
3860
3861 return false;
3862 }
3863
3864 @Override
Alan Viveretted22db212014-02-13 17:47:38 -08003865 AbsPositionScroller createPositionScroller() {
3866 return new ListViewPositionScroller();
Alan Viverette441b4372014-02-12 13:30:20 -08003867 }
3868
3869 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003870 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
3871 super.onInitializeAccessibilityEvent(event);
3872 event.setClassName(ListView.class.getName());
3873 }
3874
3875 @Override
3876 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
3877 super.onInitializeAccessibilityNodeInfo(info);
3878 info.setClassName(ListView.class.getName());
Alan Viverette5b2081d2013-08-28 10:43:07 -07003879
3880 final int count = getCount();
Alan Viverette76769ae2014-02-12 16:38:10 -08003881 final int selectionMode = getSelectionModeForAccessibility();
3882 final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false, selectionMode);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003883 info.setCollectionInfo(collectionInfo);
3884 }
3885
3886 @Override
3887 public void onInitializeAccessibilityNodeInfoForItem(
3888 View view, int position, AccessibilityNodeInfo info) {
3889 super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
3890
3891 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
3892 final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
Alan Viverette76769ae2014-02-12 16:38:10 -08003893 final boolean isSelected = isItemChecked(position);
3894 final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
3895 0, 1, position, 1, isHeading, isSelected);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003896 info.setCollectionItemInfo(itemInfo);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003897 }
Alan Viveretted22db212014-02-13 17:47:38 -08003898
3899 /**
3900 * Sub-position scroller that understands the layout of a ListView.
3901 */
3902 class ListViewPositionScroller extends AbsSubPositionScroller {
3903 @Override
3904 public int getRowForPosition(int position) {
3905 return position;
3906 }
3907
3908 @Override
3909 public int getFirstPositionForRow(int row) {
3910 return row;
3911 }
3912
3913 @Override
3914 public int getHeightForRow(int row) {
3915 return getHeightForPosition(row);
3916 }
3917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003918}