blob: 2e9858c03268616bbae3eddfc96ebe403b1f9b59 [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)) {
Alan Viverette4771b552014-08-15 18:02:23 -07001164 mRecycler.addScrapView(child, 0);
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 Viverettee3c433a2014-06-19 12:48:45 -07001721 final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
1722 || mTouchMode == TOUCH_MODE_DONE_WAITING;
1723 if (inTouchMode) {
1724 // If the user's finger is down, select the motion position.
Alan Viveretted44696c2013-07-18 10:37:15 -07001725 final View child = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee3c433a2014-06-19 12:48:45 -07001726 if (child != null) {
Alan Viveretted44696c2013-07-18 10:37:15 -07001727 positionSelector(mMotionPosition, child);
1728 }
Alan Viverettee3c433a2014-06-19 12:48:45 -07001729 } else if (mSelectorPosition != INVALID_POSITION) {
1730 // If we had previously positioned the selector somewhere,
1731 // put it back there. It might not match up with the data,
1732 // but it's transitioning out so it's not a big deal.
1733 final View child = getChildAt(mSelectorPosition - mFirstPosition);
1734 if (child != null) {
1735 positionSelector(mSelectorPosition, child);
1736 }
Romain Guy3616a412009-09-15 13:50:37 -07001737 } else {
Alan Viverettee3c433a2014-06-19 12:48:45 -07001738 // Otherwise, clear selection.
Romain Guy3616a412009-09-15 13:50:37 -07001739 mSelectedTop = 0;
1740 mSelectorRect.setEmpty();
1741 }
Alan Viverette3e141622014-02-18 17:05:13 -08001742
1743 // Even if there is not selected position, we may need to
1744 // restore focus (i.e. something focusable in touch mode).
1745 if (hasFocus() && focusLayoutRestoreView != null) {
1746 focusLayoutRestoreView.requestFocus();
1747 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001748 }
1749
Alan Viverette3e141622014-02-18 17:05:13 -08001750 // Attempt to restore accessibility focus, if necessary.
Alan Viverette2e6fc8c2014-02-24 11:09:18 -08001751 if (viewRootImpl != null) {
1752 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1753 if (newAccessibilityFocusedView == null) {
1754 if (accessibilityFocusLayoutRestoreView != null
1755 && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
1756 final AccessibilityNodeProvider provider =
1757 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1758 if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
1759 final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1760 accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1761 provider.performAction(virtualViewId,
1762 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1763 } else {
1764 accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1765 }
1766 } else if (accessibilityFocusPosition != INVALID_POSITION) {
1767 // Bound the position within the visible children.
1768 final int position = MathUtils.constrain(
1769 accessibilityFocusPosition - mFirstPosition, 0,
1770 getChildCount() - 1);
1771 final View restoreView = getChildAt(position);
1772 if (restoreView != null) {
1773 restoreView.requestAccessibilityFocus();
1774 }
Alan Viverette68207512013-08-22 12:33:07 -07001775 }
alanv30ee76c2012-09-07 10:31:16 -07001776 }
1777 }
1778
Alan Viverette3e141622014-02-18 17:05:13 -08001779 // Tell focus view we are done mucking with it, if it is still in
1780 // our view hierarchy.
1781 if (focusLayoutRestoreView != null
1782 && focusLayoutRestoreView.getWindowToken() != null) {
1783 focusLayoutRestoreView.onFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001784 }
1785
1786 mLayoutMode = LAYOUT_NORMAL;
1787 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001788 if (mPositionScrollAfterLayout != null) {
1789 post(mPositionScrollAfterLayout);
1790 mPositionScrollAfterLayout = null;
1791 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001792 mNeedSync = false;
1793 setNextSelectedPositionInt(mSelectedPosition);
1794
1795 updateScrollIndicators();
1796
1797 if (mItemCount > 0) {
1798 checkSelectionChanged();
1799 }
1800
1801 invokeOnItemScrollListener();
1802 } finally {
1803 if (!blockLayoutRequests) {
1804 mBlockLayoutRequests = false;
1805 }
1806 }
1807 }
1808
1809 /**
Alan Viverette3e141622014-02-18 17:05:13 -08001810 * @param child a direct child of this list.
1811 * @return Whether child is a header or footer view.
1812 */
1813 private boolean isDirectChildHeaderOrFooter(View child) {
1814 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1815 final int numHeaders = headers.size();
1816 for (int i = 0; i < numHeaders; i++) {
1817 if (child == headers.get(i).view) {
1818 return true;
1819 }
1820 }
1821
1822 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1823 final int numFooters = footers.size();
1824 for (int i = 0; i < numFooters; i++) {
1825 if (child == footers.get(i).view) {
1826 return true;
1827 }
1828 }
1829
1830 return false;
1831 }
1832
1833 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 * Obtain the view and add it to our list of children. The view can be made
1835 * fresh, converted from an unused view, or used as is if it was in the
1836 * recycle bin.
1837 *
1838 * @param position Logical position in the list
1839 * @param y Top or bottom edge of the view to add
1840 * @param flow If flow is true, align top edge to y. If false, align bottom
1841 * edge to y.
1842 * @param childrenLeft Left edge where children should be positioned
1843 * @param selected Is this position selected?
1844 * @return View that was added
1845 */
1846 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1847 boolean selected) {
1848 View child;
1849
1850
1851 if (!mDataChanged) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001852 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001853 child = mRecycler.getActiveView(position);
1854 if (child != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001855 // Found it -- we're using an existing child
1856 // This just needs to be positioned
1857 setupChild(child, position, y, flow, childrenLeft, selected, true);
1858
1859 return child;
1860 }
1861 }
1862
1863 // Make a new view for this position, or convert an unused view if possible
Romain Guy21875052010-01-06 18:48:08 -08001864 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001865
1866 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001867 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001868
1869 return child;
1870 }
1871
1872 /**
1873 * Add a view as a child and make sure it is measured (if necessary) and
1874 * positioned properly.
1875 *
1876 * @param child The view to add
1877 * @param position The position of this child
1878 * @param y The y position relative to which this view will be positioned
1879 * @param flowDown If true, align top edge to y. If false, align bottom
1880 * edge to y.
1881 * @param childrenLeft Left edge where children should be positioned
1882 * @param selected Is this position selected?
1883 * @param recycled Has this view been pulled from the recycle bin? If so it
1884 * does not need to be remeasured.
1885 */
1886 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1887 boolean selected, boolean recycled) {
Romain Guy5fade8c2013-07-10 16:36:18 -07001888 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
1889
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001890 final boolean isSelected = selected && shouldShowSelector();
1891 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001892 final int mode = mTouchMode;
1893 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1894 mMotionPosition == position;
1895 final boolean updateChildPressed = isPressed != child.isPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001896 final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1897
1898 // Respect layout params that are already in the view. Otherwise make some up...
1899 // noinspection unchecked
1900 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1901 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001902 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001903 }
1904 p.viewType = mAdapter.getItemViewType(position);
1905
Romain Guy0bf88592010-03-02 13:38:44 -08001906 if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001907 p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001908 attachViewToParent(child, flowDown ? -1 : 0, p);
1909 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001910 p.forceAdd = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001911 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1912 p.recycledHeaderFooter = true;
1913 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001914 addViewInLayout(child, flowDown ? -1 : 0, p, true);
1915 }
1916
1917 if (updateChildSelected) {
1918 child.setSelected(isSelected);
1919 }
1920
Romain Guy3616a412009-09-15 13:50:37 -07001921 if (updateChildPressed) {
1922 child.setPressed(isPressed);
1923 }
1924
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001925 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1926 if (child instanceof Checkable) {
1927 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001928 } else if (getContext().getApplicationInfo().targetSdkVersion
1929 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1930 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001931 }
1932 }
1933
1934 if (needToMeasure) {
1935 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
1936 mListPadding.left + mListPadding.right, p.width);
1937 int lpHeight = p.height;
1938 int childHeightSpec;
1939 if (lpHeight > 0) {
1940 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1941 } else {
1942 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1943 }
1944 child.measure(childWidthSpec, childHeightSpec);
1945 } else {
1946 cleanupLayoutState(child);
1947 }
1948
1949 final int w = child.getMeasuredWidth();
1950 final int h = child.getMeasuredHeight();
1951 final int childTop = flowDown ? y : y - h;
1952
1953 if (needToMeasure) {
1954 final int childRight = childrenLeft + w;
1955 final int childBottom = childTop + h;
1956 child.layout(childrenLeft, childTop, childRight, childBottom);
1957 } else {
1958 child.offsetLeftAndRight(childrenLeft - child.getLeft());
1959 child.offsetTopAndBottom(childTop - child.getTop());
1960 }
1961
1962 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1963 child.setDrawingCacheEnabled(true);
1964 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001965
1966 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1967 != position) {
1968 child.jumpDrawablesToCurrentState();
1969 }
Romain Guy5fade8c2013-07-10 16:36:18 -07001970
1971 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001972 }
1973
1974 @Override
1975 protected boolean canAnimate() {
1976 return super.canAnimate() && mItemCount > 0;
1977 }
1978
1979 /**
1980 * Sets the currently selected item. If in touch mode, the item will not be selected
1981 * but it will still be positioned appropriately. If the specified selection position
1982 * is less than 0, then the item at position 0 will be selected.
1983 *
1984 * @param position Index (starting at 0) of the data item to be selected.
1985 */
1986 @Override
1987 public void setSelection(int position) {
1988 setSelectionFromTop(position, 0);
1989 }
1990
1991 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001992 * Makes the item at the supplied position selected.
Mike Cleronf116bf82009-09-27 19:14:12 -07001993 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001994 * @param position the position of the item to select
1995 */
1996 @Override
1997 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001998 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07001999 boolean awakeScrollbars = false;
2000
2001 final int selectedPosition = mSelectedPosition;
2002
2003 if (selectedPosition >= 0) {
2004 if (position == selectedPosition - 1) {
2005 awakeScrollbars = true;
2006 } else if (position == selectedPosition + 1) {
2007 awakeScrollbars = true;
2008 }
2009 }
2010
Adam Powell1fa179e2012-04-12 15:01:40 -07002011 if (mPositionScroller != null) {
2012 mPositionScroller.stop();
2013 }
2014
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002015 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07002016
2017 if (awakeScrollbars) {
2018 awakenScrollBars();
2019 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002020 }
2021
2022 /**
2023 * Find a position that can be selected (i.e., is not a separator).
2024 *
2025 * @param position The starting position to look at.
2026 * @param lookDown Whether to look down for other positions.
2027 * @return The next selectable position starting at position and then searching either up or
2028 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
2029 */
2030 @Override
2031 int lookForSelectablePosition(int position, boolean lookDown) {
2032 final ListAdapter adapter = mAdapter;
2033 if (adapter == null || isInTouchMode()) {
2034 return INVALID_POSITION;
2035 }
2036
2037 final int count = adapter.getCount();
2038 if (!mAreAllItemsSelectable) {
2039 if (lookDown) {
2040 position = Math.max(0, position);
2041 while (position < count && !adapter.isEnabled(position)) {
2042 position++;
2043 }
2044 } else {
2045 position = Math.min(position, count - 1);
2046 while (position >= 0 && !adapter.isEnabled(position)) {
2047 position--;
2048 }
2049 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002050 }
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002051
2052 if (position < 0 || position >= count) {
2053 return INVALID_POSITION;
2054 }
2055
2056 return position;
2057 }
2058
2059 /**
2060 * Find a position that can be selected (i.e., is not a separator). If there
2061 * are no selectable positions in the specified direction from the starting
2062 * position, searches in the opposite direction from the starting position
2063 * to the current position.
2064 *
2065 * @param current the current position
2066 * @param position the starting position
2067 * @param lookDown whether to look down for other positions
2068 * @return the next selectable position, or {@link #INVALID_POSITION} if
2069 * nothing can be found
2070 */
2071 int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2072 final ListAdapter adapter = mAdapter;
2073 if (adapter == null || isInTouchMode()) {
2074 return INVALID_POSITION;
2075 }
2076
2077 // First check after the starting position in the specified direction.
2078 final int after = lookForSelectablePosition(position, lookDown);
2079 if (after != INVALID_POSITION) {
2080 return after;
2081 }
2082
2083 // Then check between the starting position and the current position.
2084 final int count = adapter.getCount();
2085 current = MathUtils.constrain(current, -1, count - 1);
2086 if (lookDown) {
2087 position = Math.min(position - 1, count - 1);
2088 while ((position > current) && !adapter.isEnabled(position)) {
2089 position--;
2090 }
2091 if (position <= current) {
2092 return INVALID_POSITION;
2093 }
2094 } else {
2095 position = Math.max(0, position + 1);
2096 while ((position < current) && !adapter.isEnabled(position)) {
2097 position++;
2098 }
2099 if (position >= current) {
2100 return INVALID_POSITION;
2101 }
2102 }
2103
2104 return position;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002105 }
2106
2107 /**
2108 * setSelectionAfterHeaderView set the selection to be the first list item
2109 * after the header views.
2110 */
2111 public void setSelectionAfterHeaderView() {
2112 final int count = mHeaderViewInfos.size();
2113 if (count > 0) {
2114 mNextSelectedPosition = 0;
2115 return;
2116 }
2117
2118 if (mAdapter != null) {
2119 setSelection(count);
2120 } else {
2121 mNextSelectedPosition = count;
2122 mLayoutMode = LAYOUT_SET_SELECTION;
2123 }
2124
2125 }
2126
2127 @Override
2128 public boolean dispatchKeyEvent(KeyEvent event) {
2129 // Dispatch in the normal way
2130 boolean handled = super.dispatchKeyEvent(event);
2131 if (!handled) {
2132 // If we didn't handle it...
2133 View focused = getFocusedChild();
2134 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2135 // ... and our focused child didn't handle it
2136 // ... give it to ourselves so we can scroll if necessary
2137 handled = onKeyDown(event.getKeyCode(), event);
2138 }
2139 }
2140 return handled;
2141 }
2142
2143 @Override
2144 public boolean onKeyDown(int keyCode, KeyEvent event) {
2145 return commonKey(keyCode, 1, event);
2146 }
2147
2148 @Override
2149 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2150 return commonKey(keyCode, repeatCount, event);
2151 }
2152
2153 @Override
2154 public boolean onKeyUp(int keyCode, KeyEvent event) {
2155 return commonKey(keyCode, 1, event);
2156 }
2157
2158 private boolean commonKey(int keyCode, int count, KeyEvent event) {
Adam Powell31986b52013-09-24 14:53:30 -07002159 if (mAdapter == null || !isAttachedToWindow()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002160 return false;
2161 }
2162
2163 if (mDataChanged) {
2164 layoutChildren();
2165 }
2166
2167 boolean handled = false;
2168 int action = event.getAction();
2169
2170 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002171 switch (keyCode) {
2172 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002173 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002174 handled = resurrectSelectionIfNeeded();
2175 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002176 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002177 if (arrowScroll(FOCUS_UP)) {
2178 handled = true;
2179 } else {
2180 break;
2181 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002182 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002183 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002184 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002185 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002186 }
2187 break;
2188
2189 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002190 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002191 handled = resurrectSelectionIfNeeded();
2192 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002193 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002194 if (arrowScroll(FOCUS_DOWN)) {
2195 handled = true;
2196 } else {
2197 break;
2198 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002201 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002202 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002203 }
2204 break;
2205
2206 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002207 if (event.hasNoModifiers()) {
2208 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2209 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002210 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002211
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002212 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002213 if (event.hasNoModifiers()) {
2214 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2215 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002216 break;
2217
2218 case KeyEvent.KEYCODE_DPAD_CENTER:
2219 case KeyEvent.KEYCODE_ENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002220 if (event.hasNoModifiers()) {
2221 handled = resurrectSelectionIfNeeded();
2222 if (!handled
2223 && event.getRepeatCount() == 0 && getChildCount() > 0) {
2224 keyPressed();
2225 handled = true;
2226 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228 break;
2229
2230 case KeyEvent.KEYCODE_SPACE:
2231 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002232 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002233 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002234 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002235 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002236 }
2237 handled = true;
2238 }
2239 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002240
2241 case KeyEvent.KEYCODE_PAGE_UP:
2242 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002243 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002244 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002245 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002246 }
2247 break;
2248
2249 case KeyEvent.KEYCODE_PAGE_DOWN:
2250 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002251 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002252 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002253 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002254 }
2255 break;
2256
2257 case KeyEvent.KEYCODE_MOVE_HOME:
2258 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002259 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002260 }
2261 break;
2262
2263 case KeyEvent.KEYCODE_MOVE_END:
2264 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002265 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002266 }
2267 break;
2268
2269 case KeyEvent.KEYCODE_TAB:
2270 // XXX Sometimes it is useful to be able to TAB through the items in
2271 // a ListView sequentially. Unfortunately this can create an
2272 // asymmetry in TAB navigation order unless the list selection
2273 // always reverts to the top or bottom when receiving TAB focus from
2274 // another widget. Leaving this behavior disabled for now but
2275 // perhaps it should be configurable (and more comprehensive).
2276 if (false) {
2277 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002278 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002279 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002280 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002281 }
2282 }
2283 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002284 }
2285 }
2286
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002287 if (handled) {
2288 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002290
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002291 if (sendToTextFilter(keyCode, count, event)) {
2292 return true;
2293 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002294
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002295 switch (action) {
2296 case KeyEvent.ACTION_DOWN:
2297 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002298
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002299 case KeyEvent.ACTION_UP:
2300 return super.onKeyUp(keyCode, event);
2301
2302 case KeyEvent.ACTION_MULTIPLE:
2303 return super.onKeyMultiple(keyCode, count, event);
2304
2305 default: // shouldn't happen
2306 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002307 }
2308 }
2309
2310 /**
2311 * Scrolls up or down by the number of items currently present on screen.
2312 *
2313 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2314 * @return whether selection was moved
2315 */
2316 boolean pageScroll(int direction) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002317 final int nextPage;
2318 final boolean down;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002319
2320 if (direction == FOCUS_UP) {
2321 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002322 down = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 } else if (direction == FOCUS_DOWN) {
2324 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2325 down = true;
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002326 } else {
2327 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002328 }
2329
2330 if (nextPage >= 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002331 final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002332 if (position >= 0) {
2333 mLayoutMode = LAYOUT_SPECIFIC;
2334 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2335
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002336 if (down && (position > (mItemCount - getChildCount()))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002337 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2338 }
2339
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002340 if (!down && (position < getChildCount())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002341 mLayoutMode = LAYOUT_FORCE_TOP;
2342 }
2343
2344 setSelectionInt(position);
2345 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002346 if (!awakenScrollBars()) {
2347 invalidate();
2348 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002349
2350 return true;
2351 }
2352 }
2353
2354 return false;
2355 }
2356
2357 /**
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002358 * Go to the last or first item if possible (not worrying about panning
2359 * across or navigating within the internal focus of the currently selected
2360 * item.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002361 *
2362 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002363 * @return whether selection was moved
2364 */
2365 boolean fullScroll(int direction) {
2366 boolean moved = false;
2367 if (direction == FOCUS_UP) {
2368 if (mSelectedPosition != 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002369 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002370 if (position >= 0) {
2371 mLayoutMode = LAYOUT_FORCE_TOP;
2372 setSelectionInt(position);
2373 invokeOnItemScrollListener();
2374 }
2375 moved = true;
2376 }
2377 } else if (direction == FOCUS_DOWN) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002378 final int lastItem = (mItemCount - 1);
2379 if (mSelectedPosition < lastItem) {
2380 final int position = lookForSelectablePositionAfter(
2381 mSelectedPosition, lastItem, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002382 if (position >= 0) {
2383 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2384 setSelectionInt(position);
2385 invokeOnItemScrollListener();
2386 }
2387 moved = true;
2388 }
2389 }
2390
Mike Cleronf116bf82009-09-27 19:14:12 -07002391 if (moved && !awakenScrollBars()) {
2392 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002393 invalidate();
2394 }
2395
2396 return moved;
2397 }
2398
2399 /**
2400 * To avoid horizontal focus searches changing the selected item, we
2401 * manually focus search within the selected item (as applicable), and
2402 * prevent focus from jumping to something within another item.
2403 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2404 * @return Whether this consumes the key event.
2405 */
2406 private boolean handleHorizontalFocusWithinListItem(int direction) {
2407 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002408 throw new IllegalArgumentException("direction must be one of"
2409 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002410 }
2411
2412 final int numChildren = getChildCount();
2413 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2414 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002415 if (selectedView != null && selectedView.hasFocus() &&
2416 selectedView instanceof ViewGroup) {
2417
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002418 final View currentFocus = selectedView.findFocus();
2419 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002420 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002421 if (nextFocus != null) {
2422 // do the math to get interesting rect in next focus' coordinates
2423 currentFocus.getFocusedRect(mTempRect);
2424 offsetDescendantRectToMyCoords(currentFocus, mTempRect);
2425 offsetRectIntoDescendantCoords(nextFocus, mTempRect);
2426 if (nextFocus.requestFocus(direction, mTempRect)) {
2427 return true;
2428 }
2429 }
2430 // we are blocking the key from being handled (by returning true)
2431 // if the global result is going to be some other view within this
2432 // list. this is to acheive the overall goal of having
2433 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002434 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2435 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002436 if (globalNextFocus != null) {
2437 return isViewAncestorOf(globalNextFocus, this);
2438 }
2439 }
2440 }
2441 return false;
2442 }
2443
2444 /**
2445 * Scrolls to the next or previous item if possible.
2446 *
2447 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2448 *
2449 * @return whether selection was moved
2450 */
2451 boolean arrowScroll(int direction) {
2452 try {
2453 mInLayout = true;
2454 final boolean handled = arrowScrollImpl(direction);
2455 if (handled) {
2456 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2457 }
2458 return handled;
2459 } finally {
2460 mInLayout = false;
2461 }
2462 }
2463
2464 /**
Adam Powell2a939112013-03-21 17:08:38 -07002465 * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002466 * to move to. This return a position in the direction given if the selected item
2467 * is fully visible.
Adam Powell2a939112013-03-21 17:08:38 -07002468 *
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002469 * @param selectedView Current selected view to move from
Adam Powell2a939112013-03-21 17:08:38 -07002470 * @param selectedPos Current selected position to move from
2471 * @param direction Direction to move in
2472 * @return Desired selected position after moving in the given direction
2473 */
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002474 private final int nextSelectedPositionForDirection(
2475 View selectedView, int selectedPos, int direction) {
Adam Powell2a939112013-03-21 17:08:38 -07002476 int nextSelected;
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002477
Adam Powell2a939112013-03-21 17:08:38 -07002478 if (direction == View.FOCUS_DOWN) {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002479 final int listBottom = getHeight() - mListPadding.bottom;
2480 if (selectedView != null && selectedView.getBottom() <= listBottom) {
2481 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2482 selectedPos + 1 :
2483 mFirstPosition;
2484 } else {
2485 return INVALID_POSITION;
2486 }
Adam Powell2a939112013-03-21 17:08:38 -07002487 } else {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002488 final int listTop = mListPadding.top;
2489 if (selectedView != null && selectedView.getTop() >= listTop) {
2490 final int lastPos = mFirstPosition + getChildCount() - 1;
2491 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2492 selectedPos - 1 :
2493 lastPos;
2494 } else {
2495 return INVALID_POSITION;
2496 }
Adam Powell2a939112013-03-21 17:08:38 -07002497 }
2498
2499 if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2500 return INVALID_POSITION;
2501 }
2502 return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2503 }
2504
2505 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002506 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2507 * whether there are focusable items etc.
2508 *
2509 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2510 * @return Whether any scrolling, selection or focus change occured.
2511 */
2512 private boolean arrowScrollImpl(int direction) {
2513 if (getChildCount() <= 0) {
2514 return false;
2515 }
2516
2517 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002518 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002519
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002520 int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002521 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2522
2523 // if we are moving focus, we may OVERRIDE the default behavior
2524 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2525 if (focusResult != null) {
2526 nextSelectedPosition = focusResult.getSelectedPosition();
2527 amountToScroll = focusResult.getAmountToScroll();
2528 }
2529
2530 boolean needToRedraw = focusResult != null;
2531 if (nextSelectedPosition != INVALID_POSITION) {
2532 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2533 setSelectedPositionInt(nextSelectedPosition);
2534 setNextSelectedPositionInt(nextSelectedPosition);
2535 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002536 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002537 if (mItemsCanFocus && focusResult == null) {
2538 // there was no new view found to take focus, make sure we
2539 // don't leave focus with the old selection
2540 final View focused = getFocusedChild();
2541 if (focused != null) {
2542 focused.clearFocus();
2543 }
2544 }
2545 needToRedraw = true;
2546 checkSelectionChanged();
2547 }
2548
2549 if (amountToScroll > 0) {
2550 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2551 needToRedraw = true;
2552 }
2553
2554 // if we didn't find a new focusable, make sure any existing focused
2555 // item that was panned off screen gives up focus.
2556 if (mItemsCanFocus && (focusResult == null)
2557 && selectedView != null && selectedView.hasFocus()) {
2558 final View focused = selectedView.findFocus();
Mark Brophy1ea68892011-08-01 16:24:44 +01002559 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002560 focused.clearFocus();
2561 }
2562 }
2563
2564 // if the current selection is panned off, we need to remove the selection
2565 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2566 && !isViewAncestorOf(selectedView, this)) {
2567 selectedView = null;
2568 hideSelector();
2569
2570 // but we don't want to set the ressurect position (that would make subsequent
2571 // unhandled key events bring back the item we just scrolled off!)
2572 mResurrectToPosition = INVALID_POSITION;
2573 }
2574
2575 if (needToRedraw) {
2576 if (selectedView != null) {
Alan Viverettede399392014-05-01 17:20:55 -07002577 positionSelectorLikeFocus(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002578 mSelectedTop = selectedView.getTop();
2579 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002580 if (!awakenScrollBars()) {
2581 invalidate();
2582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002583 invokeOnItemScrollListener();
2584 return true;
2585 }
2586
2587 return false;
2588 }
2589
2590 /**
2591 * When selection changes, it is possible that the previously selected or the
2592 * next selected item will change its size. If so, we need to offset some folks,
2593 * and re-layout the items as appropriate.
2594 *
2595 * @param selectedView The currently selected view (before changing selection).
2596 * should be <code>null</code> if there was no previous selection.
2597 * @param direction Either {@link android.view.View#FOCUS_UP} or
2598 * {@link android.view.View#FOCUS_DOWN}.
2599 * @param newSelectedPosition The position of the next selection.
2600 * @param newFocusAssigned whether new focus was assigned. This matters because
2601 * when something has focus, we don't want to show selection (ugh).
2602 */
2603 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2604 boolean newFocusAssigned) {
2605 if (newSelectedPosition == INVALID_POSITION) {
2606 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2607 }
2608
2609 // whether or not we are moving down or up, we want to preserve the
2610 // top of whatever view is on top:
2611 // - moving down: the view that had selection
2612 // - moving up: the view that is getting selection
2613 View topView;
2614 View bottomView;
2615 int topViewIndex, bottomViewIndex;
2616 boolean topSelected = false;
2617 final int selectedIndex = mSelectedPosition - mFirstPosition;
2618 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2619 if (direction == View.FOCUS_UP) {
2620 topViewIndex = nextSelectedIndex;
2621 bottomViewIndex = selectedIndex;
2622 topView = getChildAt(topViewIndex);
2623 bottomView = selectedView;
2624 topSelected = true;
2625 } else {
2626 topViewIndex = selectedIndex;
2627 bottomViewIndex = nextSelectedIndex;
2628 topView = selectedView;
2629 bottomView = getChildAt(bottomViewIndex);
2630 }
2631
2632 final int numChildren = getChildCount();
2633
2634 // start with top view: is it changing size?
2635 if (topView != null) {
2636 topView.setSelected(!newFocusAssigned && topSelected);
2637 measureAndAdjustDown(topView, topViewIndex, numChildren);
2638 }
2639
2640 // is the bottom view changing size?
2641 if (bottomView != null) {
2642 bottomView.setSelected(!newFocusAssigned && !topSelected);
2643 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2644 }
2645 }
2646
2647 /**
2648 * Re-measure a child, and if its height changes, lay it out preserving its
2649 * top, and adjust the children below it appropriately.
2650 * @param child The child
2651 * @param childIndex The view group index of the child.
2652 * @param numChildren The number of children in the view group.
2653 */
2654 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2655 int oldHeight = child.getHeight();
2656 measureItem(child);
2657 if (child.getMeasuredHeight() != oldHeight) {
2658 // lay out the view, preserving its top
2659 relayoutMeasuredItem(child);
2660
2661 // adjust views below appropriately
2662 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2663 for (int i = childIndex + 1; i < numChildren; i++) {
2664 getChildAt(i).offsetTopAndBottom(heightDelta);
2665 }
2666 }
2667 }
2668
2669 /**
2670 * Measure a particular list child.
2671 * TODO: unify with setUpChild.
2672 * @param child The child.
2673 */
2674 private void measureItem(View child) {
2675 ViewGroup.LayoutParams p = child.getLayoutParams();
2676 if (p == null) {
2677 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002678 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002679 ViewGroup.LayoutParams.WRAP_CONTENT);
2680 }
2681
2682 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2683 mListPadding.left + mListPadding.right, p.width);
2684 int lpHeight = p.height;
2685 int childHeightSpec;
2686 if (lpHeight > 0) {
2687 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2688 } else {
2689 childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
2690 }
2691 child.measure(childWidthSpec, childHeightSpec);
2692 }
2693
2694 /**
2695 * Layout a child that has been measured, preserving its top position.
2696 * TODO: unify with setUpChild.
2697 * @param child The child.
2698 */
2699 private void relayoutMeasuredItem(View child) {
2700 final int w = child.getMeasuredWidth();
2701 final int h = child.getMeasuredHeight();
2702 final int childLeft = mListPadding.left;
2703 final int childRight = childLeft + w;
2704 final int childTop = child.getTop();
2705 final int childBottom = childTop + h;
2706 child.layout(childLeft, childTop, childRight, childBottom);
2707 }
2708
2709 /**
2710 * @return The amount to preview next items when arrow srolling.
2711 */
2712 private int getArrowScrollPreviewLength() {
2713 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2714 }
2715
2716 /**
2717 * Determine how much we need to scroll in order to get the next selected view
2718 * visible, with a fading edge showing below as applicable. The amount is
2719 * capped at {@link #getMaxScrollAmount()} .
2720 *
2721 * @param direction either {@link android.view.View#FOCUS_UP} or
2722 * {@link android.view.View#FOCUS_DOWN}.
2723 * @param nextSelectedPosition The position of the next selection, or
2724 * {@link #INVALID_POSITION} if there is no next selectable position
2725 * @return The amount to scroll. Note: this is always positive! Direction
2726 * needs to be taken into account when actually scrolling.
2727 */
2728 private int amountToScroll(int direction, int nextSelectedPosition) {
2729 final int listBottom = getHeight() - mListPadding.bottom;
2730 final int listTop = mListPadding.top;
2731
Justin Ho1ad11b92013-02-25 16:09:20 -08002732 int numChildren = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002733
2734 if (direction == View.FOCUS_DOWN) {
2735 int indexToMakeVisible = numChildren - 1;
2736 if (nextSelectedPosition != INVALID_POSITION) {
2737 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2738 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002739 while (numChildren <= indexToMakeVisible) {
2740 // Child to view is not attached yet.
2741 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2742 numChildren++;
2743 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002744 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2745 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2746
2747 int goalBottom = listBottom;
2748 if (positionToMakeVisible < mItemCount - 1) {
2749 goalBottom -= getArrowScrollPreviewLength();
2750 }
2751
2752 if (viewToMakeVisible.getBottom() <= goalBottom) {
2753 // item is fully visible.
2754 return 0;
2755 }
2756
2757 if (nextSelectedPosition != INVALID_POSITION
2758 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2759 // item already has enough of it visible, changing selection is good enough
2760 return 0;
2761 }
2762
2763 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2764
2765 if ((mFirstPosition + numChildren) == mItemCount) {
2766 // last is last in list -> make sure we don't scroll past it
2767 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2768 amountToScroll = Math.min(amountToScroll, max);
2769 }
2770
2771 return Math.min(amountToScroll, getMaxScrollAmount());
2772 } else {
2773 int indexToMakeVisible = 0;
2774 if (nextSelectedPosition != INVALID_POSITION) {
2775 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2776 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002777 while (indexToMakeVisible < 0) {
2778 // Child to view is not attached yet.
2779 addViewAbove(getChildAt(0), mFirstPosition);
2780 mFirstPosition--;
2781 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2782 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002783 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2784 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2785 int goalTop = listTop;
2786 if (positionToMakeVisible > 0) {
2787 goalTop += getArrowScrollPreviewLength();
2788 }
2789 if (viewToMakeVisible.getTop() >= goalTop) {
2790 // item is fully visible.
2791 return 0;
2792 }
2793
2794 if (nextSelectedPosition != INVALID_POSITION &&
2795 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2796 // item already has enough of it visible, changing selection is good enough
2797 return 0;
2798 }
2799
2800 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2801 if (mFirstPosition == 0) {
2802 // first is first in list -> make sure we don't scroll past it
2803 final int max = listTop - getChildAt(0).getTop();
2804 amountToScroll = Math.min(amountToScroll, max);
2805 }
2806 return Math.min(amountToScroll, getMaxScrollAmount());
2807 }
2808 }
2809
2810 /**
2811 * Holds results of focus aware arrow scrolling.
2812 */
2813 static private class ArrowScrollFocusResult {
2814 private int mSelectedPosition;
2815 private int mAmountToScroll;
2816
2817 /**
2818 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2819 */
2820 void populate(int selectedPosition, int amountToScroll) {
2821 mSelectedPosition = selectedPosition;
2822 mAmountToScroll = amountToScroll;
2823 }
2824
2825 public int getSelectedPosition() {
2826 return mSelectedPosition;
2827 }
2828
2829 public int getAmountToScroll() {
2830 return mAmountToScroll;
2831 }
2832 }
2833
2834 /**
2835 * @param direction either {@link android.view.View#FOCUS_UP} or
2836 * {@link android.view.View#FOCUS_DOWN}.
2837 * @return The position of the next selectable position of the views that
2838 * are currently visible, taking into account the fact that there might
2839 * be no selection. Returns {@link #INVALID_POSITION} if there is no
2840 * selectable view on screen in the given direction.
2841 */
2842 private int lookForSelectablePositionOnScreen(int direction) {
2843 final int firstPosition = mFirstPosition;
2844 if (direction == View.FOCUS_DOWN) {
2845 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2846 mSelectedPosition + 1 :
2847 firstPosition;
2848 if (startPos >= mAdapter.getCount()) {
2849 return INVALID_POSITION;
2850 }
2851 if (startPos < firstPosition) {
2852 startPos = firstPosition;
2853 }
2854
2855 final int lastVisiblePos = getLastVisiblePosition();
2856 final ListAdapter adapter = getAdapter();
2857 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2858 if (adapter.isEnabled(pos)
2859 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2860 return pos;
2861 }
2862 }
2863 } else {
2864 int last = firstPosition + getChildCount() - 1;
2865 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2866 mSelectedPosition - 1 :
2867 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08002868 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002869 return INVALID_POSITION;
2870 }
2871 if (startPos > last) {
2872 startPos = last;
2873 }
2874
2875 final ListAdapter adapter = getAdapter();
2876 for (int pos = startPos; pos >= firstPosition; pos--) {
2877 if (adapter.isEnabled(pos)
2878 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2879 return pos;
2880 }
2881 }
2882 }
2883 return INVALID_POSITION;
2884 }
2885
2886 /**
2887 * Do an arrow scroll based on focus searching. If a new view is
2888 * given focus, return the selection delta and amount to scroll via
2889 * an {@link ArrowScrollFocusResult}, otherwise, return null.
2890 *
2891 * @param direction either {@link android.view.View#FOCUS_UP} or
2892 * {@link android.view.View#FOCUS_DOWN}.
2893 * @return The result if focus has changed, or <code>null</code>.
2894 */
2895 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2896 final View selectedView = getSelectedView();
2897 View newFocus;
2898 if (selectedView != null && selectedView.hasFocus()) {
2899 View oldFocus = selectedView.findFocus();
2900 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2901 } else {
2902 if (direction == View.FOCUS_DOWN) {
2903 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2904 final int listTop = mListPadding.top +
2905 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2906 final int ySearchPoint =
2907 (selectedView != null && selectedView.getTop() > listTop) ?
2908 selectedView.getTop() :
2909 listTop;
2910 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2911 } else {
2912 final boolean bottomFadingEdgeShowing =
2913 (mFirstPosition + getChildCount() - 1) < mItemCount;
2914 final int listBottom = getHeight() - mListPadding.bottom -
2915 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2916 final int ySearchPoint =
2917 (selectedView != null && selectedView.getBottom() < listBottom) ?
2918 selectedView.getBottom() :
2919 listBottom;
2920 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2921 }
2922 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2923 }
2924
2925 if (newFocus != null) {
2926 final int positionOfNewFocus = positionOfNewFocus(newFocus);
2927
2928 // if the focus change is in a different new position, make sure
2929 // we aren't jumping over another selectable position
2930 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2931 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2932 if (selectablePosition != INVALID_POSITION &&
2933 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2934 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2935 return null;
2936 }
2937 }
2938
2939 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2940
2941 final int maxScrollAmount = getMaxScrollAmount();
2942 if (focusScroll < maxScrollAmount) {
2943 // not moving too far, safe to give next view focus
2944 newFocus.requestFocus(direction);
2945 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2946 return mArrowScrollFocusResult;
2947 } else if (distanceToView(newFocus) < maxScrollAmount){
2948 // Case to consider:
2949 // too far to get entire next focusable on screen, but by going
2950 // max scroll amount, we are getting it at least partially in view,
2951 // so give it focus and scroll the max ammount.
2952 newFocus.requestFocus(direction);
2953 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2954 return mArrowScrollFocusResult;
2955 }
2956 }
2957 return null;
2958 }
2959
2960 /**
2961 * @param newFocus The view that would have focus.
2962 * @return the position that contains newFocus
2963 */
2964 private int positionOfNewFocus(View newFocus) {
2965 final int numChildren = getChildCount();
2966 for (int i = 0; i < numChildren; i++) {
2967 final View child = getChildAt(i);
2968 if (isViewAncestorOf(newFocus, child)) {
2969 return mFirstPosition + i;
2970 }
2971 }
2972 throw new IllegalArgumentException("newFocus is not a child of any of the"
2973 + " children of the list!");
2974 }
2975
2976 /**
2977 * Return true if child is an ancestor of parent, (or equal to the parent).
2978 */
2979 private boolean isViewAncestorOf(View child, View parent) {
2980 if (child == parent) {
2981 return true;
2982 }
2983
2984 final ViewParent theParent = child.getParent();
2985 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
2986 }
2987
2988 /**
2989 * Determine how much we need to scroll in order to get newFocus in view.
2990 * @param direction either {@link android.view.View#FOCUS_UP} or
2991 * {@link android.view.View#FOCUS_DOWN}.
2992 * @param newFocus The view that would take focus.
2993 * @param positionOfNewFocus The position of the list item containing newFocus
2994 * @return The amount to scroll. Note: this is always positive! Direction
2995 * needs to be taken into account when actually scrolling.
2996 */
2997 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
2998 int amountToScroll = 0;
2999 newFocus.getDrawingRect(mTempRect);
3000 offsetDescendantRectToMyCoords(newFocus, mTempRect);
3001 if (direction == View.FOCUS_UP) {
3002 if (mTempRect.top < mListPadding.top) {
3003 amountToScroll = mListPadding.top - mTempRect.top;
3004 if (positionOfNewFocus > 0) {
3005 amountToScroll += getArrowScrollPreviewLength();
3006 }
3007 }
3008 } else {
3009 final int listBottom = getHeight() - mListPadding.bottom;
3010 if (mTempRect.bottom > listBottom) {
3011 amountToScroll = mTempRect.bottom - listBottom;
3012 if (positionOfNewFocus < mItemCount - 1) {
3013 amountToScroll += getArrowScrollPreviewLength();
3014 }
3015 }
3016 }
3017 return amountToScroll;
3018 }
3019
3020 /**
3021 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003022 * direction.
3023 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003024 * @param descendant A descendant of this list.
3025 * @return The distance, or 0 if the nearest edge is already on screen.
3026 */
3027 private int distanceToView(View descendant) {
3028 int distance = 0;
3029 descendant.getDrawingRect(mTempRect);
3030 offsetDescendantRectToMyCoords(descendant, mTempRect);
3031 final int listBottom = mBottom - mTop - mListPadding.bottom;
3032 if (mTempRect.bottom < mListPadding.top) {
3033 distance = mListPadding.top - mTempRect.bottom;
3034 } else if (mTempRect.top > listBottom) {
3035 distance = mTempRect.top - listBottom;
3036 }
3037 return distance;
3038 }
3039
3040
3041 /**
3042 * Scroll the children by amount, adding a view at the end and removing
3043 * views that fall off as necessary.
3044 *
3045 * @param amount The amount (positive or negative) to scroll.
3046 */
3047 private void scrollListItemsBy(int amount) {
3048 offsetChildrenTopAndBottom(amount);
3049
3050 final int listBottom = getHeight() - mListPadding.bottom;
3051 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003052 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003053
3054 if (amount < 0) {
3055 // shifted items up
3056
3057 // may need to pan views into the bottom space
3058 int numChildren = getChildCount();
3059 View last = getChildAt(numChildren - 1);
3060 while (last.getBottom() < listBottom) {
3061 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3062 if (lastVisiblePosition < mItemCount - 1) {
3063 last = addViewBelow(last, lastVisiblePosition);
3064 numChildren++;
3065 } else {
3066 break;
3067 }
3068 }
3069
3070 // may have brought in the last child of the list that is skinnier
3071 // than the fading edge, thereby leaving space at the end. need
3072 // to shift back
3073 if (last.getBottom() < listBottom) {
3074 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3075 }
3076
3077 // top views may be panned off screen
3078 View first = getChildAt(0);
3079 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003080 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3081 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003082 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003083 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003084 detachViewFromParent(first);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003085 first = getChildAt(0);
3086 mFirstPosition++;
3087 }
3088 } else {
3089 // shifted items down
3090 View first = getChildAt(0);
3091
3092 // may need to pan views into top
3093 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3094 first = addViewAbove(first, mFirstPosition);
3095 mFirstPosition--;
3096 }
3097
3098 // may have brought the very first child of the list in too far and
3099 // need to shift it back
3100 if (first.getTop() > listTop) {
3101 offsetChildrenTopAndBottom(listTop - first.getTop());
3102 }
3103
3104 int lastIndex = getChildCount() - 1;
3105 View last = getChildAt(lastIndex);
3106
3107 // bottom view may be panned off screen
3108 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003109 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3110 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003111 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003112 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003113 detachViewFromParent(last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003114 last = getChildAt(--lastIndex);
3115 }
3116 }
3117 }
3118
3119 private View addViewAbove(View theView, int position) {
3120 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08003121 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003122 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003123 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3124 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003125 return view;
3126 }
3127
3128 private View addViewBelow(View theView, int position) {
3129 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08003130 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003131 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003132 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3133 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003134 return view;
3135 }
3136
3137 /**
3138 * Indicates that the views created by the ListAdapter can contain focusable
3139 * items.
3140 *
3141 * @param itemsCanFocus true if items can get focus, false otherwise
3142 */
3143 public void setItemsCanFocus(boolean itemsCanFocus) {
3144 mItemsCanFocus = itemsCanFocus;
3145 if (!itemsCanFocus) {
3146 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3147 }
3148 }
3149
3150 /**
3151 * @return Whether the views created by the ListAdapter can contain focusable
3152 * items.
3153 */
3154 public boolean getItemsCanFocus() {
3155 return mItemsCanFocus;
3156 }
3157
3158 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003159 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003160 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f2009-05-15 16:03:59 -07003161 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003162 if (retValue) {
3163 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d2011-03-07 15:36:33 -08003164 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003165 View first = getChildAt(0);
3166 if (first == null || first.getTop() > listTop) {
3167 return false;
3168 }
Adam Powell3ba8f5d2011-03-07 15:36:33 -08003169 final int listBottom = getHeight() -
3170 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003171 View last = getChildAt(getChildCount() - 1);
3172 if (last == null || last.getBottom() < listBottom) {
3173 return false;
3174 }
3175 }
3176 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003177 }
3178
3179 @Override
3180 public void setCacheColorHint(int color) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003181 final boolean opaque = (color >>> 24) == 0xFF;
3182 mIsCacheColorOpaque = opaque;
3183 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003184 if (mDividerPaint == null) {
3185 mDividerPaint = new Paint();
3186 }
Romain Guy8f1344f2009-05-15 16:03:59 -07003187 mDividerPaint.setColor(color);
3188 }
Romain Guy24443ea2009-05-11 11:56:30 -07003189 super.setCacheColorHint(color);
3190 }
Adam Powell637d3372010-08-25 14:37:03 -07003191
3192 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3193 final int height = drawable.getMinimumHeight();
3194
3195 canvas.save();
3196 canvas.clipRect(bounds);
3197
3198 final int span = bounds.bottom - bounds.top;
3199 if (span < height) {
3200 bounds.top = bounds.bottom - height;
3201 }
3202
3203 drawable.setBounds(bounds);
3204 drawable.draw(canvas);
3205
3206 canvas.restore();
3207 }
3208
3209 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3210 final int height = drawable.getMinimumHeight();
3211
3212 canvas.save();
3213 canvas.clipRect(bounds);
3214
3215 final int span = bounds.bottom - bounds.top;
3216 if (span < height) {
3217 bounds.bottom = bounds.top + height;
3218 }
3219
3220 drawable.setBounds(bounds);
3221 drawable.draw(canvas);
3222
3223 canvas.restore();
3224 }
3225
Romain Guy24443ea2009-05-11 11:56:30 -07003226 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003227 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003228 if (mCachingStarted) {
3229 mCachingActive = true;
3230 }
3231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003232 // Draw the dividers
3233 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003234 final Drawable overscrollHeader = mOverScrollHeader;
3235 final Drawable overscrollFooter = mOverScrollFooter;
3236 final boolean drawOverscrollHeader = overscrollHeader != null;
3237 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003238 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003239
Adam Powell637d3372010-08-25 14:37:03 -07003240 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003241 // Only modify the top and bottom in the loop, we set the left and right here
3242 final Rect bounds = mTempRect;
3243 bounds.left = mPaddingLeft;
3244 bounds.right = mRight - mLeft - mPaddingRight;
3245
3246 final int count = getChildCount();
3247 final int headerCount = mHeaderViewInfos.size();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003248 final int itemCount = mItemCount;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003249 final int footerLimit = (itemCount - mFooterViewInfos.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003250 final boolean headerDividers = mHeaderDividersEnabled;
3251 final boolean footerDividers = mFooterDividersEnabled;
3252 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003253 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3254 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003255 // If the list is opaque *and* the background is not, we want to
3256 // fill a rect where the dividers would be for non-selectable items
3257 // If the list is opaque and the background is also opaque, we don't
3258 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003259 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003260
3261 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003262 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003263 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003264 }
Romain Guy8f1344f2009-05-15 16:03:59 -07003265 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003266
Adam Powell94566552011-01-05 23:25:33 -08003267 int effectivePaddingTop = 0;
3268 int effectivePaddingBottom = 0;
3269 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3270 effectivePaddingTop = mListPadding.top;
3271 effectivePaddingBottom = mListPadding.bottom;
3272 }
3273
3274 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003275 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003276 int bottom = 0;
Adam Powell0b8bb422010-02-08 14:30:45 -08003277
Adam Powell637d3372010-08-25 14:37:03 -07003278 // Draw top divider or header for overscroll
3279 final int scrollY = mScrollY;
3280 if (count > 0 && scrollY < 0) {
3281 if (drawOverscrollHeader) {
3282 bounds.bottom = 0;
3283 bounds.top = scrollY;
3284 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3285 } else if (drawDividers) {
3286 bounds.bottom = 0;
3287 bounds.top = -dividerHeight;
3288 drawDivider(canvas, bounds, -1);
3289 }
3290 }
3291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003292 for (int i = 0; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003293 final int itemIndex = (first + i);
3294 final boolean isHeader = (itemIndex < headerCount);
3295 final boolean isFooter = (itemIndex >= footerLimit);
3296 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3297 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003298 bottom = child.getBottom();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003299 final boolean isLastItem = (i == (count - 1));
Adam Powell637d3372010-08-25 14:37:03 -07003300
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003301 if (drawDividers && (bottom < listBottom)
3302 && !(drawOverscrollFooter && isLastItem)) {
3303 final int nextIndex = (itemIndex + 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003304 // Draw dividers between enabled items, headers
3305 // and/or footers when enabled and requested, and
3306 // after the last enabled item.
3307 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3308 && (nextIndex >= headerCount)) && (isLastItem
3309 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3310 && (nextIndex < footerLimit)))) {
Adam Powell637d3372010-08-25 14:37:03 -07003311 bounds.top = bottom;
3312 bounds.bottom = bottom + dividerHeight;
3313 drawDivider(canvas, bounds, i);
3314 } else if (fillForMissingDividers) {
3315 bounds.top = bottom;
3316 bounds.bottom = bottom + dividerHeight;
3317 canvas.drawRect(bounds, paint);
3318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003319 }
3320 }
3321 }
Adam Powell637d3372010-08-25 14:37:03 -07003322
3323 final int overFooterBottom = mBottom + mScrollY;
3324 if (drawOverscrollFooter && first + count == itemCount &&
3325 overFooterBottom > bottom) {
3326 bounds.top = bottom;
3327 bounds.bottom = overFooterBottom;
3328 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3329 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003330 } else {
3331 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003332
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003333 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003334
Adam Powell637d3372010-08-25 14:37:03 -07003335 if (count > 0 && drawOverscrollHeader) {
3336 bounds.top = scrollY;
3337 bounds.bottom = getChildAt(0).getTop();
3338 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3339 }
3340
3341 final int start = drawOverscrollHeader ? 1 : 0;
3342 for (int i = start; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003343 final int itemIndex = (first + i);
3344 final boolean isHeader = (itemIndex < headerCount);
3345 final boolean isFooter = (itemIndex >= footerLimit);
3346 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3347 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003348 top = child.getTop();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003349 if (drawDividers && (top > effectivePaddingTop)) {
3350 final boolean isFirstItem = (i == start);
3351 final int previousIndex = (itemIndex - 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003352 // Draw dividers between enabled items, headers
3353 // and/or footers when enabled and requested, and
3354 // before the first enabled item.
3355 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3356 && (previousIndex >= headerCount)) && (isFirstItem ||
3357 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3358 && (previousIndex < footerLimit)))) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003359 bounds.top = top - dividerHeight;
3360 bounds.bottom = top;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003361 // Give the method the child ABOVE the divider,
3362 // so we subtract one from our child position.
3363 // Give -1 when there is no child above the
Romain Guy8f1344f2009-05-15 16:03:59 -07003364 // divider.
3365 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003366 } else if (fillForMissingDividers) {
Romain Guy8f1344f2009-05-15 16:03:59 -07003367 bounds.top = top - dividerHeight;
3368 bounds.bottom = top;
3369 canvas.drawRect(bounds, paint);
3370 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003371 }
3372 }
3373 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003374
Romain Guy179de8a2010-07-09 13:27:00 -07003375 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003376 if (drawOverscrollFooter) {
3377 final int absListBottom = mBottom;
3378 bounds.top = absListBottom;
3379 bounds.bottom = absListBottom + scrollY;
3380 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3381 } else if (drawDividers) {
3382 bounds.top = listBottom;
3383 bounds.bottom = listBottom + dividerHeight;
3384 drawDivider(canvas, bounds, -1);
3385 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003386 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003387 }
3388 }
3389
3390 // Draw the indicators (these should be drawn above the dividers) and children
3391 super.dispatchDraw(canvas);
3392 }
3393
Romain Guy0211a0a2011-02-14 16:34:59 -08003394 @Override
3395 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3396 boolean more = super.drawChild(canvas, child, drawingTime);
3397 if (mCachingActive && child.mCachingFailed) {
3398 mCachingActive = false;
3399 }
3400 return more;
3401 }
3402
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003403 /**
3404 * Draws a divider for the given child in the given bounds.
3405 *
3406 * @param canvas The canvas to draw to.
3407 * @param bounds The bounds of the divider.
3408 * @param childIndex The index of child (of the View) above the divider.
3409 * This will be -1 if there is no child above the divider to be
3410 * drawn.
3411 */
3412 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3413 // This widget draws the same divider for all children
3414 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003415
Romain Guy95930e12010-10-04 13:46:02 -07003416 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003417 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003418 }
3419
3420 /**
3421 * Returns the drawable that will be drawn between each item in the list.
3422 *
3423 * @return the current drawable drawn between list elements
3424 */
3425 public Drawable getDivider() {
3426 return mDivider;
3427 }
3428
3429 /**
3430 * Sets the drawable that will be drawn between each item in the list. If the drawable does
3431 * not have an intrinsic height, you should also call {@link #setDividerHeight(int)}
3432 *
3433 * @param divider The drawable to use.
3434 */
3435 public void setDivider(Drawable divider) {
3436 if (divider != null) {
3437 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003438 } else {
3439 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003440 }
3441 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003442 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003443 requestLayout();
3444 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 }
3446
3447 /**
3448 * @return Returns the height of the divider that will be drawn between each item in the list.
3449 */
3450 public int getDividerHeight() {
3451 return mDividerHeight;
3452 }
3453
3454 /**
3455 * Sets the height of the divider that will be drawn between each item in the list. Calling
3456 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3457 *
3458 * @param height The new height of the divider in pixels.
3459 */
3460 public void setDividerHeight(int height) {
3461 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003462 requestLayout();
3463 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003464 }
3465
3466 /**
3467 * Enables or disables the drawing of the divider for header views.
3468 *
3469 * @param headerDividersEnabled True to draw the headers, false otherwise.
3470 *
3471 * @see #setFooterDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003472 * @see #areHeaderDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 * @see #addHeaderView(android.view.View)
3474 */
3475 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3476 mHeaderDividersEnabled = headerDividersEnabled;
3477 invalidate();
3478 }
3479
3480 /**
Alan Viverette55421032013-06-11 17:50:56 -07003481 * @return Whether the drawing of the divider for header views is enabled
3482 *
3483 * @see #setHeaderDividersEnabled(boolean)
3484 */
3485 public boolean areHeaderDividersEnabled() {
3486 return mHeaderDividersEnabled;
3487 }
3488
3489 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003490 * Enables or disables the drawing of the divider for footer views.
3491 *
3492 * @param footerDividersEnabled True to draw the footers, false otherwise.
3493 *
3494 * @see #setHeaderDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003495 * @see #areFooterDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003496 * @see #addFooterView(android.view.View)
3497 */
3498 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3499 mFooterDividersEnabled = footerDividersEnabled;
3500 invalidate();
3501 }
Alan Viverette55421032013-06-11 17:50:56 -07003502
3503 /**
3504 * @return Whether the drawing of the divider for footer views is enabled
3505 *
3506 * @see #setFooterDividersEnabled(boolean)
3507 */
3508 public boolean areFooterDividersEnabled() {
3509 return mFooterDividersEnabled;
3510 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003511
Adam Powell637d3372010-08-25 14:37:03 -07003512 /**
3513 * Sets the drawable that will be drawn above all other list content.
3514 * This area can become visible when the user overscrolls the list.
3515 *
3516 * @param header The drawable to use
3517 */
3518 public void setOverscrollHeader(Drawable header) {
3519 mOverScrollHeader = header;
3520 if (mScrollY < 0) {
3521 invalidate();
3522 }
3523 }
3524
3525 /**
3526 * @return The drawable that will be drawn above all other list content
3527 */
3528 public Drawable getOverscrollHeader() {
3529 return mOverScrollHeader;
3530 }
3531
3532 /**
3533 * Sets the drawable that will be drawn below all other list content.
3534 * This area can become visible when the user overscrolls the list,
3535 * or when the list's content does not fully fill the container area.
3536 *
3537 * @param footer The drawable to use
3538 */
3539 public void setOverscrollFooter(Drawable footer) {
3540 mOverScrollFooter = footer;
3541 invalidate();
3542 }
3543
3544 /**
3545 * @return The drawable that will be drawn below all other list content
3546 */
3547 public Drawable getOverscrollFooter() {
3548 return mOverScrollFooter;
3549 }
3550
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003551 @Override
3552 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3553 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3554
Adam Powelle1bf4862011-09-02 16:56:20 -07003555 final ListAdapter adapter = mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003556 int closetChildIndex = -1;
Adam Powelldcce1212011-10-31 16:41:21 -07003557 int closestChildTop = 0;
Adam Powelle1bf4862011-09-02 16:56:20 -07003558 if (adapter != null && gainFocus && previouslyFocusedRect != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003559 previouslyFocusedRect.offset(mScrollX, mScrollY);
3560
Adam Powelld7507832010-02-18 15:40:33 -08003561 // Don't cache the result of getChildCount or mFirstPosition here,
3562 // it could change in layoutChildren.
3563 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003564 mLayoutMode = LAYOUT_NORMAL;
3565 layoutChildren();
3566 }
3567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003568 // figure out which item should be selected based on previously
3569 // focused rect
3570 Rect otherRect = mTempRect;
3571 int minDistance = Integer.MAX_VALUE;
3572 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003573 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003574
3575 for (int i = 0; i < childCount; i++) {
3576 // only consider selectable views
3577 if (!adapter.isEnabled(firstPosition + i)) {
3578 continue;
3579 }
3580
3581 View other = getChildAt(i);
3582 other.getDrawingRect(otherRect);
3583 offsetDescendantRectToMyCoords(other, otherRect);
3584 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3585
3586 if (distance < minDistance) {
3587 minDistance = distance;
3588 closetChildIndex = i;
Adam Powelldcce1212011-10-31 16:41:21 -07003589 closestChildTop = other.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003590 }
3591 }
3592 }
3593
3594 if (closetChildIndex >= 0) {
Adam Powelldcce1212011-10-31 16:41:21 -07003595 setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003596 } else {
3597 requestLayout();
3598 }
3599 }
3600
3601
3602 /*
3603 * (non-Javadoc)
3604 *
3605 * Children specified in XML are assumed to be header views. After we have
3606 * parsed them move them out of the children list and into mHeaderViews.
3607 */
3608 @Override
3609 protected void onFinishInflate() {
3610 super.onFinishInflate();
3611
3612 int count = getChildCount();
3613 if (count > 0) {
3614 for (int i = 0; i < count; ++i) {
3615 addHeaderView(getChildAt(i));
3616 }
3617 removeAllViews();
3618 }
3619 }
3620
3621 /* (non-Javadoc)
3622 * @see android.view.View#findViewById(int)
3623 * First look in our children, then in any header and footer views that may be scrolled off.
3624 */
3625 @Override
3626 protected View findViewTraversal(int id) {
3627 View v;
3628 v = super.findViewTraversal(id);
3629 if (v == null) {
3630 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3631 if (v != null) {
3632 return v;
3633 }
3634 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3635 if (v != null) {
3636 return v;
3637 }
3638 }
3639 return v;
3640 }
3641
3642 /* (non-Javadoc)
3643 *
3644 * Look in the passed in list of headers or footers for the view.
3645 */
3646 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3647 if (where != null) {
3648 int len = where.size();
3649 View v;
3650
3651 for (int i = 0; i < len; i++) {
3652 v = where.get(i).view;
3653
3654 if (!v.isRootNamespace()) {
3655 v = v.findViewById(id);
3656
3657 if (v != null) {
3658 return v;
3659 }
3660 }
3661 }
3662 }
3663 return null;
3664 }
3665
3666 /* (non-Javadoc)
Jeff Brown4e6319b2010-12-13 10:36:51 -08003667 * @see android.view.View#findViewWithTag(Object)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003668 * First look in our children, then in any header and footer views that may be scrolled off.
3669 */
3670 @Override
3671 protected View findViewWithTagTraversal(Object tag) {
3672 View v;
3673 v = super.findViewWithTagTraversal(tag);
3674 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003675 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003676 if (v != null) {
3677 return v;
3678 }
3679
Jeff Brown4e6319b2010-12-13 10:36:51 -08003680 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003681 if (v != null) {
3682 return v;
3683 }
3684 }
3685 return v;
3686 }
3687
3688 /* (non-Javadoc)
3689 *
3690 * Look in the passed in list of headers or footers for the view with the tag.
3691 */
Jeff Brown4e6319b2010-12-13 10:36:51 -08003692 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003693 if (where != null) {
3694 int len = where.size();
3695 View v;
3696
3697 for (int i = 0; i < len; i++) {
3698 v = where.get(i).view;
3699
3700 if (!v.isRootNamespace()) {
3701 v = v.findViewWithTag(tag);
3702
3703 if (v != null) {
3704 return v;
3705 }
3706 }
3707 }
3708 }
3709 return null;
3710 }
3711
Jeff Brown4e6319b2010-12-13 10:36:51 -08003712 /**
3713 * @hide
3714 * @see android.view.View#findViewByPredicate(Predicate)
3715 * First look in our children, then in any header and footer views that may be scrolled off.
3716 */
3717 @Override
Jeff Brown4dfbec22011-08-15 14:55:37 -07003718 protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003719 View v;
Jeff Brown4dfbec22011-08-15 14:55:37 -07003720 v = super.findViewByPredicateTraversal(predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003721 if (v == null) {
Jeff Brown4dfbec22011-08-15 14:55:37 -07003722 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003723 if (v != null) {
3724 return v;
3725 }
3726
Jeff Brown4dfbec22011-08-15 14:55:37 -07003727 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003728 if (v != null) {
3729 return v;
3730 }
3731 }
3732 return v;
3733 }
3734
3735 /* (non-Javadoc)
3736 *
3737 * Look in the passed in list of headers or footers for the first view that matches
3738 * the predicate.
3739 */
3740 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
Jeff Brown4dfbec22011-08-15 14:55:37 -07003741 Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003742 if (where != null) {
3743 int len = where.size();
3744 View v;
3745
3746 for (int i = 0; i < len; i++) {
3747 v = where.get(i).view;
3748
Jeff Brown4dfbec22011-08-15 14:55:37 -07003749 if (v != childToSkip && !v.isRootNamespace()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003750 v = v.findViewByPredicate(predicate);
3751
3752 if (v != null) {
3753 return v;
3754 }
3755 }
3756 }
3757 }
3758 return null;
3759 }
3760
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003761 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003762 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003763 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3764 *
3765 * @return A new array which contains the id of each checked item in the
3766 * list.
3767 *
Adam Powell463ceff2010-03-09 11:50:51 -08003768 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003769 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003770 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003771 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003772 // Use new behavior that correctly handles stable ID mapping.
3773 if (mAdapter != null && mAdapter.hasStableIds()) {
3774 return getCheckedItemIds();
3775 }
3776
3777 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3778 // Fall back to it to support legacy apps.
3779 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3780 final SparseBooleanArray states = mCheckStates;
3781 final int count = states.size();
3782 final long[] ids = new long[count];
3783 final ListAdapter adapter = mAdapter;
3784
3785 int checkedCount = 0;
3786 for (int i = 0; i < count; i++) {
3787 if (states.valueAt(i)) {
3788 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3789 }
3790 }
3791
3792 // Trim array if needed. mCheckStates may contain false values
3793 // resulting in checkedCount being smaller than count.
3794 if (checkedCount == count) {
3795 return ids;
3796 } else {
3797 final long[] result = new long[checkedCount];
3798 System.arraycopy(ids, 0, result, 0, checkedCount);
3799
3800 return result;
3801 }
3802 }
3803 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08003804 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003805
3806 @Override
Alan Viverette441b4372014-02-12 13:30:20 -08003807 int getHeightForPosition(int position) {
3808 final int height = super.getHeightForPosition(position);
3809 if (shouldAdjustHeightForDivider(position)) {
3810 return height + mDividerHeight;
3811 }
3812 return height;
3813 }
3814
3815 private boolean shouldAdjustHeightForDivider(int itemIndex) {
3816 final int dividerHeight = mDividerHeight;
3817 final Drawable overscrollHeader = mOverScrollHeader;
3818 final Drawable overscrollFooter = mOverScrollFooter;
3819 final boolean drawOverscrollHeader = overscrollHeader != null;
3820 final boolean drawOverscrollFooter = overscrollFooter != null;
3821 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3822
3823 if (drawDividers) {
3824 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3825 final int itemCount = mItemCount;
3826 final int headerCount = mHeaderViewInfos.size();
3827 final int footerLimit = (itemCount - mFooterViewInfos.size());
3828 final boolean isHeader = (itemIndex < headerCount);
3829 final boolean isFooter = (itemIndex >= footerLimit);
3830 final boolean headerDividers = mHeaderDividersEnabled;
3831 final boolean footerDividers = mFooterDividersEnabled;
3832 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3833 final ListAdapter adapter = mAdapter;
3834 if (!mStackFromBottom) {
3835 final boolean isLastItem = (itemIndex == (itemCount - 1));
3836 if (!drawOverscrollFooter || !isLastItem) {
3837 final int nextIndex = itemIndex + 1;
3838 // Draw dividers between enabled items, headers
3839 // and/or footers when enabled and requested, and
3840 // after the last enabled item.
3841 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3842 && (nextIndex >= headerCount)) && (isLastItem
3843 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3844 && (nextIndex < footerLimit)))) {
3845 return true;
3846 } else if (fillForMissingDividers) {
3847 return true;
3848 }
3849 }
3850 } else {
3851 final int start = drawOverscrollHeader ? 1 : 0;
3852 final boolean isFirstItem = (itemIndex == start);
3853 if (!isFirstItem) {
3854 final int previousIndex = (itemIndex - 1);
3855 // Draw dividers between enabled items, headers
3856 // and/or footers when enabled and requested, and
3857 // before the first enabled item.
3858 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3859 && (previousIndex >= headerCount)) && (isFirstItem ||
3860 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3861 && (previousIndex < footerLimit)))) {
3862 return true;
3863 } else if (fillForMissingDividers) {
3864 return true;
3865 }
3866 }
3867 }
3868 }
3869 }
3870
3871 return false;
3872 }
3873
3874 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003875 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
3876 super.onInitializeAccessibilityEvent(event);
3877 event.setClassName(ListView.class.getName());
3878 }
3879
3880 @Override
3881 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
3882 super.onInitializeAccessibilityNodeInfo(info);
3883 info.setClassName(ListView.class.getName());
Alan Viverette5b2081d2013-08-28 10:43:07 -07003884
Alan Viverette77c180a2014-09-08 15:30:34 -07003885 final int rowsCount = getCount();
Alan Viverette76769ae2014-02-12 16:38:10 -08003886 final int selectionMode = getSelectionModeForAccessibility();
Alan Viverette77c180a2014-09-08 15:30:34 -07003887 final CollectionInfo collectionInfo = CollectionInfo.obtain(
3888 rowsCount, 1, false, selectionMode);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003889 info.setCollectionInfo(collectionInfo);
3890 }
3891
3892 @Override
3893 public void onInitializeAccessibilityNodeInfoForItem(
3894 View view, int position, AccessibilityNodeInfo info) {
3895 super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
3896
3897 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
3898 final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
Alan Viverette76769ae2014-02-12 16:38:10 -08003899 final boolean isSelected = isItemChecked(position);
3900 final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
Alan Viverette77c180a2014-09-08 15:30:34 -07003901 position, 1, 0, 1, isHeading, isSelected);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003902 info.setCollectionItemInfo(itemInfo);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003903 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003904}