blob: b43ea76976e1cd566b2ec50634488b535e420762 [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
Alan Viverette7b2f8642015-06-01 10:30:21 -070019import android.annotation.Nullable;
Alan Viverette23f44322015-04-06 16:04:56 -070020import android.os.Bundle;
Romain Guy5fade8c2013-07-10 16:36:18 -070021import android.os.Trace;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080022import com.android.internal.R;
Jeff Brown4e6319b2010-12-13 10:36:51 -080023import com.android.internal.util.Predicate;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080024import com.google.android.collect.Lists;
25
Tor Norbye7b9c9122013-05-30 16:48:33 -070026import android.annotation.IdRes;
Siva Velusamy94a6d152015-05-05 15:07:00 -070027import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070029import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.content.res.TypedArray;
31import android.graphics.Canvas;
Romain Guy8f1344f52009-05-15 16:03:59 -070032import android.graphics.Paint;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080033import android.graphics.PixelFormat;
34import android.graphics.Rect;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080035import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.util.AttributeSet;
alanv30ee76c2012-09-07 10:31:16 -070037import android.util.MathUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.util.SparseBooleanArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.view.FocusFinder;
40import android.view.KeyEvent;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080041import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.view.View;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070045import android.view.ViewHierarchyEncoder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.view.ViewParent;
Alan Viverette3e141622014-02-18 17:05:13 -080047import android.view.ViewRootImpl;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080048import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070049import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette5b2081d2013-08-28 10:43:07 -070050import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
51import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
Alan Viverette3e141622014-02-18 17:05:13 -080052import android.view.accessibility.AccessibilityNodeProvider;
Winson Chung499cb9f2010-07-16 11:18:17 -070053import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import java.util.ArrayList;
56
57/*
58 * Implementation Notes:
59 *
60 * Some terminology:
61 *
62 * index - index of the items that are currently visible
63 * position - index of the items in the cursor
64 */
65
66
67/**
68 * A view that shows items in a vertically scrolling list. The items
69 * come from the {@link ListAdapter} associated with this view.
70 *
Scott Main4c359b72012-07-24 15:51:27 -070071 * <p>See the <a href="{@docRoot}guide/topics/ui/layout/listview.html">List View</a>
72 * guide.</p>
Scott Main41ec6532010-08-19 16:57:07 -070073 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 * @attr ref android.R.styleable#ListView_entries
75 * @attr ref android.R.styleable#ListView_divider
76 * @attr ref android.R.styleable#ListView_dividerHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 * @attr ref android.R.styleable#ListView_headerDividersEnabled
78 * @attr ref android.R.styleable#ListView_footerDividersEnabled
79 */
Winson Chung499cb9f2010-07-16 11:18:17 -070080@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081public class ListView extends AbsListView {
82 /**
83 * Used to indicate a no preference for a position type.
84 */
85 static final int NO_POSITION = -1;
86
87 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 * When arrow scrolling, ListView will never scroll more than this factor
89 * times the height of the list.
90 */
91 private static final float MAX_SCROLL_FACTOR = 0.33f;
92
93 /**
94 * When arrow scrolling, need a certain amount of pixels to preview next
95 * items. This is usually the fading edge, but if that is small enough,
96 * we want to make sure we preview at least this many pixels.
97 */
98 private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
99
100 /**
101 * A class that represents a fixed view in a list, for example a header at the top
102 * or a footer at the bottom.
103 */
104 public class FixedViewInfo {
105 /** The view to add to the list */
106 public View view;
107 /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
108 public Object data;
109 /** <code>true</code> if the fixed view should be selectable in the list */
110 public boolean isSelectable;
111 }
112
113 private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
114 private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
115
116 Drawable mDivider;
117 int mDividerHeight;
Adam Cohenfb603862010-12-17 12:03:17 -0800118
Adam Powell637d3372010-08-25 14:37:03 -0700119 Drawable mOverScrollHeader;
120 Drawable mOverScrollFooter;
121
Romain Guy24443ea2009-05-11 11:56:30 -0700122 private boolean mIsCacheColorOpaque;
123 private boolean mDividerIsOpaque;
Romain Guy24443ea2009-05-11 11:56:30 -0700124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 private boolean mHeaderDividersEnabled;
126 private boolean mFooterDividersEnabled;
127
128 private boolean mAreAllItemsSelectable = true;
129
130 private boolean mItemsCanFocus = false;
131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 // used for temporary calculations.
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700133 private final Rect mTempRect = new Rect();
Romain Guya02903f2009-05-23 13:26:46 -0700134 private Paint mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
136 // the single allocated result per list view; kinda cheesey but avoids
137 // allocating these thingies too often.
Romain Guy9c3184cc2010-02-25 17:32:54 -0800138 private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139
Adam Powell9bf3c122010-02-26 11:32:07 -0800140 // Keeps focused children visible through resizes
141 private FocusSelector mFocusSelector;
Adam Powell8350f7d2010-07-28 14:27:28 -0700142
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 public ListView(Context context) {
144 this(context, null);
145 }
146
147 public ListView(Context context, AttributeSet attrs) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700148 this(context, attrs, R.attr.listViewStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 }
150
Alan Viverette617feb92013-09-09 18:09:13 -0700151 public ListView(Context context, AttributeSet attrs, int defStyleAttr) {
152 this(context, attrs, defStyleAttr, 0);
153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154
Alan Viverette617feb92013-09-09 18:09:13 -0700155 public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
156 super(context, attrs, defStyleAttr, defStyleRes);
157
158 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7b2f8642015-06-01 10:30:21 -0700159 attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160
Alan Viverette7b2f8642015-06-01 10:30:21 -0700161 final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 if (entries != null) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700163 setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 }
165
Alan Viverette7b2f8642015-06-01 10:30:21 -0700166 final Drawable d = a.getDrawable(R.styleable.ListView_divider);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 if (d != null) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700168 // Use an implicit divider height which may be explicitly
169 // overridden by android:dividerHeight further down.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 setDivider(d);
171 }
Alan Viverette7b2f8642015-06-01 10:30:21 -0700172
173 final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
Adam Powell637d3372010-08-25 14:37:03 -0700174 if (osHeader != null) {
175 setOverscrollHeader(osHeader);
176 }
177
Alan Viverette7b2f8642015-06-01 10:30:21 -0700178 final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
Adam Powell637d3372010-08-25 14:37:03 -0700179 if (osFooter != null) {
180 setOverscrollFooter(osFooter);
181 }
182
Alan Viverette7b2f8642015-06-01 10:30:21 -0700183 // Use an explicit divider height, if specified.
184 if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
185 final int dividerHeight = a.getDimensionPixelSize(
186 R.styleable.ListView_dividerHeight, 0);
187 if (dividerHeight != 0) {
188 setDividerHeight(dividerHeight);
189 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 }
191
192 mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
193 mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
194
195 a.recycle();
196 }
197
198 /**
199 * @return The maximum amount a list view will scroll in response to
200 * an arrow event.
201 */
202 public int getMaxScrollAmount() {
203 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
204 }
205
206 /**
207 * Make sure views are touching the top or bottom edge, as appropriate for
208 * our gravity
209 */
210 private void adjustViewsUpOrDown() {
211 final int childCount = getChildCount();
212 int delta;
213
214 if (childCount > 0) {
215 View child;
216
217 if (!mStackFromBottom) {
218 // Uh-oh -- we came up short. Slide all views up to make them
219 // align with the top
220 child = getChildAt(0);
221 delta = child.getTop() - mListPadding.top;
222 if (mFirstPosition != 0) {
223 // It's OK to have some space above the first item if it is
224 // part of the vertical spacing
225 delta -= mDividerHeight;
226 }
227 if (delta < 0) {
228 // We only are looking to see if we are too low, not too high
229 delta = 0;
230 }
231 } else {
232 // we are too high, slide all views down to align with bottom
233 child = getChildAt(childCount - 1);
234 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
235
236 if (mFirstPosition + childCount < mItemCount) {
237 // It's OK to have some space below the last item if it is
238 // part of the vertical spacing
239 delta += mDividerHeight;
240 }
241
242 if (delta > 0) {
243 delta = 0;
244 }
245 }
246
247 if (delta != 0) {
248 offsetChildrenTopAndBottom(-delta);
249 }
250 }
251 }
252
253 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700254 * 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 -0800255 * called more than once, the views will appear in the order they were
256 * added. Views added using this call can take focus if they want.
257 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700258 * Note: When first introduced, this method could only be called before
259 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700260 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700261 * called at any time. If the ListView's adapter does not extend
262 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
263 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 *
265 * @param v The view to add.
266 * @param data Data to associate with this view
267 * @param isSelectable whether the item is selectable
268 */
269 public void addHeaderView(View v, Object data, boolean isSelectable) {
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700270 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 info.view = v;
272 info.data = data;
273 info.isSelectable = isSelectable;
274 mHeaderViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800275 mAreAllItemsSelectable &= isSelectable;
Marco Nelissen22c04a32011-04-19 14:07:55 -0700276
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700277 // Wrap the adapter if it wasn't already wrapped.
278 if (mAdapter != null) {
279 if (!(mAdapter instanceof HeaderViewListAdapter)) {
280 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
281 }
282
283 // In the case of re-adding a header view, or adding one later on,
284 // we need to notify the observer.
285 if (mDataSetObserver != null) {
286 mDataSetObserver.onChanged();
287 }
Marco Nelissen22c04a32011-04-19 14:07:55 -0700288 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 }
290
291 /**
292 * Add a fixed view to appear at the top of the list. If addHeaderView is
293 * called more than once, the views will appear in the order they were
294 * added. Views added using this call can take focus if they want.
295 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700296 * Note: When first introduced, this method could only be called before
297 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700298 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700299 * called at any time. If the ListView's adapter does not extend
300 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
301 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 *
303 * @param v The view to add.
304 */
305 public void addHeaderView(View v) {
306 addHeaderView(v, null, true);
307 }
308
309 @Override
310 public int getHeaderViewsCount() {
311 return mHeaderViewInfos.size();
312 }
313
314 /**
315 * Removes a previously-added header view.
316 *
317 * @param v The view to remove
318 * @return true if the view was removed, false if the view was not a header
319 * view
320 */
321 public boolean removeHeaderView(View v) {
322 if (mHeaderViewInfos.size() > 0) {
323 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700324 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700325 if (mDataSetObserver != null) {
326 mDataSetObserver.onChanged();
327 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 result = true;
329 }
330 removeFixedViewInfo(v, mHeaderViewInfos);
331 return result;
332 }
333 return false;
334 }
335
336 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
337 int len = where.size();
338 for (int i = 0; i < len; ++i) {
339 FixedViewInfo info = where.get(i);
340 if (info.view == v) {
341 where.remove(i);
342 break;
343 }
344 }
345 }
346
347 /**
348 * Add a fixed view to appear at the bottom of the list. If addFooterView is
349 * called more than once, the views will appear in the order they were
350 * added. Views added using this call can take focus if they want.
351 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700352 * Note: When first introduced, this method could only be called before
353 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700354 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700355 * called at any time. If the ListView's adapter does not extend
356 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
357 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 *
359 * @param v The view to add.
360 * @param data Data to associate with this view
361 * @param isSelectable true if the footer view can be selected
362 */
363 public void addFooterView(View v, Object data, boolean isSelectable) {
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700364 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 info.view = v;
366 info.data = data;
367 info.isSelectable = isSelectable;
368 mFooterViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800369 mAreAllItemsSelectable &= isSelectable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700371 // Wrap the adapter if it wasn't already wrapped.
372 if (mAdapter != null) {
373 if (!(mAdapter instanceof HeaderViewListAdapter)) {
374 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
375 }
376
377 // In the case of re-adding a footer view, or adding one later on,
378 // we need to notify the observer.
379 if (mDataSetObserver != null) {
380 mDataSetObserver.onChanged();
381 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 }
383 }
384
385 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700386 * Add a fixed view to appear at the bottom of the list. If addFooterView is
387 * called more than once, the views will appear in the order they were
388 * added. Views added using this call can take focus if they want.
389 * <p>
390 * Note: When first introduced, this method could only be called before
391 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700392 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700393 * called at any time. If the ListView's adapter does not extend
394 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
395 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 *
397 * @param v The view to add.
398 */
399 public void addFooterView(View v) {
400 addFooterView(v, null, true);
401 }
402
403 @Override
404 public int getFooterViewsCount() {
405 return mFooterViewInfos.size();
406 }
407
408 /**
409 * Removes a previously-added footer view.
410 *
411 * @param v The view to remove
412 * @return
413 * true if the view was removed, false if the view was not a footer view
414 */
415 public boolean removeFooterView(View v) {
416 if (mFooterViewInfos.size() > 0) {
417 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700418 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700419 if (mDataSetObserver != null) {
420 mDataSetObserver.onChanged();
421 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 result = true;
423 }
424 removeFixedViewInfo(v, mFooterViewInfos);
425 return result;
426 }
427 return false;
428 }
429
430 /**
431 * Returns the adapter currently in use in this ListView. The returned adapter
432 * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
433 * might be a {@link WrapperListAdapter}.
434 *
435 * @return The adapter currently used to display data in this ListView.
436 *
437 * @see #setAdapter(ListAdapter)
438 */
439 @Override
440 public ListAdapter getAdapter() {
441 return mAdapter;
442 }
443
444 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700445 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
446 * through the specified intent.
447 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
448 */
449 @android.view.RemotableViewMethod
450 public void setRemoteViewsAdapter(Intent intent) {
451 super.setRemoteViewsAdapter(intent);
452 }
453
454 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 * Sets the data behind this ListView.
456 *
457 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
458 * depending on the ListView features currently in use. For instance, adding
459 * headers and/or footers will cause the adapter to be wrapped.
460 *
461 * @param adapter The ListAdapter which is responsible for maintaining the
462 * data backing this list and for producing a view to represent an
463 * item in that data set.
464 *
465 * @see #getAdapter()
466 */
467 @Override
468 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700469 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 mAdapter.unregisterDataSetObserver(mDataSetObserver);
471 }
472
473 resetList();
474 mRecycler.clear();
475
476 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
477 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
478 } else {
479 mAdapter = adapter;
480 }
481
482 mOldSelectedPosition = INVALID_POSITION;
483 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700484
485 // AbsListView#setAdapter will update choice mode states.
486 super.setAdapter(adapter);
487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 if (mAdapter != null) {
489 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
490 mOldItemCount = mItemCount;
491 mItemCount = mAdapter.getCount();
492 checkFocus();
493
494 mDataSetObserver = new AdapterDataSetObserver();
495 mAdapter.registerDataSetObserver(mDataSetObserver);
496
497 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
498
499 int position;
500 if (mStackFromBottom) {
501 position = lookForSelectablePosition(mItemCount - 1, false);
502 } else {
503 position = lookForSelectablePosition(0, true);
504 }
505 setSelectedPositionInt(position);
506 setNextSelectedPositionInt(position);
507
508 if (mItemCount == 0) {
509 // Nothing selected
510 checkSelectionChanged();
511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 } else {
513 mAreAllItemsSelectable = true;
514 checkFocus();
515 // Nothing selected
516 checkSelectionChanged();
517 }
518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 requestLayout();
520 }
521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 /**
523 * The list is empty. Clear everything out.
524 */
525 @Override
526 void resetList() {
Romain Guy2e447d42009-04-28 18:01:24 -0700527 // The parent's resetList() will remove all views from the layout so we need to
528 // cleanup the state of our footers and headers
529 clearRecycledState(mHeaderViewInfos);
530 clearRecycledState(mFooterViewInfos);
531
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 super.resetList();
Romain Guy2e447d42009-04-28 18:01:24 -0700533
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 mLayoutMode = LAYOUT_NORMAL;
535 }
536
Romain Guy2e447d42009-04-28 18:01:24 -0700537 private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
538 if (infos != null) {
539 final int count = infos.size();
540
541 for (int i = 0; i < count; i++) {
542 final View child = infos.get(i).view;
543 final LayoutParams p = (LayoutParams) child.getLayoutParams();
544 if (p != null) {
545 p.recycledHeaderFooter = false;
546 }
547 }
548 }
549 }
550
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 /**
552 * @return Whether the list needs to show the top fading edge
553 */
554 private boolean showingTopFadingEdge() {
555 final int listTop = mScrollY + mListPadding.top;
556 return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
557 }
558
559 /**
560 * @return Whether the list needs to show the bottom fading edge
561 */
562 private boolean showingBottomFadingEdge() {
563 final int childCount = getChildCount();
564 final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
565 final int lastVisiblePosition = mFirstPosition + childCount - 1;
566
567 final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
568
569 return (lastVisiblePosition < mItemCount - 1)
570 || (bottomOfBottomChild < listBottom);
571 }
572
573
574 @Override
575 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
576
577 int rectTopWithinChild = rect.top;
578
579 // offset so rect is in coordinates of the this view
580 rect.offset(child.getLeft(), child.getTop());
581 rect.offset(-child.getScrollX(), -child.getScrollY());
582
583 final int height = getHeight();
584 int listUnfadedTop = getScrollY();
585 int listUnfadedBottom = listUnfadedTop + height;
586 final int fadingEdge = getVerticalFadingEdgeLength();
587
588 if (showingTopFadingEdge()) {
589 // leave room for top fading edge as long as rect isn't at very top
590 if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
591 listUnfadedTop += fadingEdge;
592 }
593 }
594
595 int childCount = getChildCount();
596 int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
597
598 if (showingBottomFadingEdge()) {
599 // leave room for bottom fading edge as long as rect isn't at very bottom
600 if ((mSelectedPosition < mItemCount - 1)
601 || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
602 listUnfadedBottom -= fadingEdge;
603 }
604 }
605
606 int scrollYDelta = 0;
607
608 if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
609 // need to MOVE DOWN to get it in view: move down just enough so
610 // that the entire rectangle is in view (or at least the first
611 // screen size chunk).
612
613 if (rect.height() > height) {
614 // just enough to get screen size chunk on
615 scrollYDelta += (rect.top - listUnfadedTop);
616 } else {
617 // get entire rect at bottom of screen
618 scrollYDelta += (rect.bottom - listUnfadedBottom);
619 }
620
621 // make sure we aren't scrolling beyond the end of our children
622 int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
623 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
624 } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
625 // need to MOVE UP to get it in view: move up just enough so that
626 // entire rectangle is in view (or at least the first screen
627 // size chunk of it).
628
629 if (rect.height() > height) {
630 // screen size chunk
631 scrollYDelta -= (listUnfadedBottom - rect.bottom);
632 } else {
633 // entire rect at top
634 scrollYDelta -= (listUnfadedTop - rect.top);
635 }
636
637 // make sure we aren't scrolling any further than the top our children
638 int top = getChildAt(0).getTop();
639 int deltaToTop = top - listUnfadedTop;
640 scrollYDelta = Math.max(scrollYDelta, deltaToTop);
641 }
642
643 final boolean scroll = scrollYDelta != 0;
644 if (scroll) {
645 scrollListItemsBy(-scrollYDelta);
Dianne Hackborn079e2352010-10-18 17:02:43 -0700646 positionSelector(INVALID_POSITION, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800647 mSelectedTop = child.getTop();
648 invalidate();
649 }
650 return scroll;
651 }
652
653 /**
654 * {@inheritDoc}
655 */
656 @Override
657 void fillGap(boolean down) {
658 final int count = getChildCount();
659 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800660 int paddingTop = 0;
661 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
662 paddingTop = getListPaddingTop();
663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800664 final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800665 paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 fillDown(mFirstPosition + count, startOffset);
667 correctTooHigh(getChildCount());
668 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800669 int paddingBottom = 0;
670 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
671 paddingBottom = getListPaddingBottom();
672 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800673 final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800674 getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 fillUp(mFirstPosition - 1, startOffset);
676 correctTooLow(getChildCount());
677 }
678 }
679
680 /**
681 * Fills the list from pos down to the end of the list view.
682 *
683 * @param pos The first position to put in the list
684 *
685 * @param nextTop The location where the top of the item associated with pos
686 * should be drawn
687 *
688 * @return The view that is currently selected, if it happens to be in the
689 * range that we draw.
690 */
691 private View fillDown(int pos, int nextTop) {
692 View selectedView = null;
693
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800694 int end = (mBottom - mTop);
695 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
696 end -= mListPadding.bottom;
697 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698
699 while (nextTop < end && pos < mItemCount) {
700 // is this the selected item?
701 boolean selected = pos == mSelectedPosition;
702 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
703
704 nextTop = child.getBottom() + mDividerHeight;
705 if (selected) {
706 selectedView = child;
707 }
708 pos++;
709 }
710
Adam Cohenb9673922012-01-05 13:58:47 -0800711 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 return selectedView;
713 }
714
715 /**
716 * Fills the list from pos up to the top of the list view.
717 *
718 * @param pos The first position to put in the list
719 *
720 * @param nextBottom The location where the bottom of the item associated
721 * with pos should be drawn
722 *
723 * @return The view that is currently selected
724 */
725 private View fillUp(int pos, int nextBottom) {
726 View selectedView = null;
727
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800728 int end = 0;
729 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
730 end = mListPadding.top;
731 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732
733 while (nextBottom > end && pos >= 0) {
734 // is this the selected item?
735 boolean selected = pos == mSelectedPosition;
736 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
737 nextBottom = child.getTop() - mDividerHeight;
738 if (selected) {
739 selectedView = child;
740 }
741 pos--;
742 }
743
744 mFirstPosition = pos + 1;
Adam Cohenb9673922012-01-05 13:58:47 -0800745 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 return selectedView;
747 }
748
749 /**
750 * Fills the list from top to bottom, starting with mFirstPosition
751 *
752 * @param nextTop The location where the top of the first item should be
753 * drawn
754 *
755 * @return The view that is currently selected
756 */
757 private View fillFromTop(int nextTop) {
758 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
759 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
760 if (mFirstPosition < 0) {
761 mFirstPosition = 0;
762 }
763 return fillDown(mFirstPosition, nextTop);
764 }
765
766
767 /**
768 * Put mSelectedPosition in the middle of the screen and then build up and
769 * down from there. This method forces mSelectedPosition to the center.
770 *
771 * @param childrenTop Top of the area in which children can be drawn, as
772 * measured in pixels
773 * @param childrenBottom Bottom of the area in which children can be drawn,
774 * as measured in pixels
775 * @return Currently selected view
776 */
777 private View fillFromMiddle(int childrenTop, int childrenBottom) {
778 int height = childrenBottom - childrenTop;
779
780 int position = reconcileSelectedPosition();
781
782 View sel = makeAndAddView(position, childrenTop, true,
783 mListPadding.left, true);
784 mFirstPosition = position;
785
786 int selHeight = sel.getMeasuredHeight();
787 if (selHeight <= height) {
788 sel.offsetTopAndBottom((height - selHeight) / 2);
789 }
790
791 fillAboveAndBelow(sel, position);
792
793 if (!mStackFromBottom) {
794 correctTooHigh(getChildCount());
795 } else {
796 correctTooLow(getChildCount());
797 }
798
799 return sel;
800 }
801
802 /**
803 * Once the selected view as been placed, fill up the visible area above and
804 * below it.
805 *
806 * @param sel The selected view
807 * @param position The position corresponding to sel
808 */
809 private void fillAboveAndBelow(View sel, int position) {
810 final int dividerHeight = mDividerHeight;
811 if (!mStackFromBottom) {
812 fillUp(position - 1, sel.getTop() - dividerHeight);
813 adjustViewsUpOrDown();
814 fillDown(position + 1, sel.getBottom() + dividerHeight);
815 } else {
816 fillDown(position + 1, sel.getBottom() + dividerHeight);
817 adjustViewsUpOrDown();
818 fillUp(position - 1, sel.getTop() - dividerHeight);
819 }
820 }
821
822
823 /**
824 * Fills the grid based on positioning the new selection at a specific
825 * location. The selection may be moved so that it does not intersect the
826 * faded edges. The grid is then filled upwards and downwards from there.
827 *
828 * @param selectedTop Where the selected item should be
829 * @param childrenTop Where to start drawing children
830 * @param childrenBottom Last pixel where children can be drawn
831 * @return The view that currently has selection
832 */
833 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
834 int fadingEdgeLength = getVerticalFadingEdgeLength();
835 final int selectedPosition = mSelectedPosition;
836
837 View sel;
838
839 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
840 selectedPosition);
841 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
842 selectedPosition);
843
844 sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
845
846
847 // Some of the newly selected item extends below the bottom of the list
848 if (sel.getBottom() > bottomSelectionPixel) {
849 // Find space available above the selection into which we can scroll
850 // upwards
851 final int spaceAbove = sel.getTop() - topSelectionPixel;
852
853 // Find space required to bring the bottom of the selected item
854 // fully into view
855 final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
856 final int offset = Math.min(spaceAbove, spaceBelow);
857
858 // Now offset the selected item to get it into view
859 sel.offsetTopAndBottom(-offset);
860 } else if (sel.getTop() < topSelectionPixel) {
861 // Find space required to bring the top of the selected item fully
862 // into view
863 final int spaceAbove = topSelectionPixel - sel.getTop();
864
865 // Find space available below the selection into which we can scroll
866 // downwards
867 final int spaceBelow = bottomSelectionPixel - sel.getBottom();
868 final int offset = Math.min(spaceAbove, spaceBelow);
869
870 // Offset the selected item to get it into view
871 sel.offsetTopAndBottom(offset);
872 }
873
874 // Fill in views above and below
875 fillAboveAndBelow(sel, selectedPosition);
876
877 if (!mStackFromBottom) {
878 correctTooHigh(getChildCount());
879 } else {
880 correctTooLow(getChildCount());
881 }
882
883 return sel;
884 }
885
886 /**
887 * Calculate the bottom-most pixel we can draw the selection into
888 *
889 * @param childrenBottom Bottom pixel were children can be drawn
890 * @param fadingEdgeLength Length of the fading edge in pixels, if present
891 * @param selectedPosition The position that will be selected
892 * @return The bottom-most pixel we can draw the selection into
893 */
894 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
895 int selectedPosition) {
896 int bottomSelectionPixel = childrenBottom;
897 if (selectedPosition != mItemCount - 1) {
898 bottomSelectionPixel -= fadingEdgeLength;
899 }
900 return bottomSelectionPixel;
901 }
902
903 /**
904 * Calculate the top-most pixel we can draw the selection into
905 *
906 * @param childrenTop Top pixel were children can be drawn
907 * @param fadingEdgeLength Length of the fading edge in pixels, if present
908 * @param selectedPosition The position that will be selected
909 * @return The top-most pixel we can draw the selection into
910 */
911 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
912 // first pixel we can draw the selection into
913 int topSelectionPixel = childrenTop;
914 if (selectedPosition > 0) {
915 topSelectionPixel += fadingEdgeLength;
916 }
917 return topSelectionPixel;
918 }
919
Winson Chung499cb9f2010-07-16 11:18:17 -0700920 /**
921 * Smoothly scroll to the specified adapter position. The view will
922 * scroll such that the indicated position is displayed.
923 * @param position Scroll to this adapter position.
924 */
925 @android.view.RemotableViewMethod
926 public void smoothScrollToPosition(int position) {
927 super.smoothScrollToPosition(position);
928 }
929
930 /**
931 * Smoothly scroll to the specified adapter position offset. The view will
932 * scroll such that the indicated position is displayed.
933 * @param offset The amount to offset from the adapter position to scroll to.
934 */
935 @android.view.RemotableViewMethod
936 public void smoothScrollByOffset(int offset) {
937 super.smoothScrollByOffset(offset);
938 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939
940 /**
941 * Fills the list based on positioning the new selection relative to the old
942 * selection. The new selection will be placed at, above, or below the
943 * location of the new selection depending on how the selection is moving.
944 * The selection will then be pinned to the visible part of the screen,
945 * excluding the edges that are faded. The list is then filled upwards and
946 * downwards from there.
947 *
948 * @param oldSel The old selected view. Useful for trying to put the new
949 * selection in the same place
950 * @param newSel The view that is to become selected. Useful for trying to
951 * put the new selection in the same place
952 * @param delta Which way we are moving
953 * @param childrenTop Where to start drawing children
954 * @param childrenBottom Last pixel where children can be drawn
955 * @return The view that currently has selection
956 */
957 private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
958 int childrenBottom) {
959 int fadingEdgeLength = getVerticalFadingEdgeLength();
960 final int selectedPosition = mSelectedPosition;
961
962 View sel;
963
964 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
965 selectedPosition);
966 final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
967 selectedPosition);
968
969 if (delta > 0) {
970 /*
971 * Case 1: Scrolling down.
972 */
973
974 /*
975 * Before After
976 * | | | |
977 * +-------+ +-------+
978 * | A | | A |
979 * | 1 | => +-------+
980 * +-------+ | B |
981 * | B | | 2 |
982 * +-------+ +-------+
983 * | | | |
984 *
985 * Try to keep the top of the previously selected item where it was.
986 * oldSel = A
987 * sel = B
988 */
989
990 // Put oldSel (A) where it belongs
991 oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
992 mListPadding.left, false);
993
994 final int dividerHeight = mDividerHeight;
995
996 // Now put the new selection (B) below that
997 sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
998 mListPadding.left, true);
999
1000 // Some of the newly selected item extends below the bottom of the list
1001 if (sel.getBottom() > bottomSelectionPixel) {
1002
1003 // Find space available above the selection into which we can scroll upwards
1004 int spaceAbove = sel.getTop() - topSelectionPixel;
1005
1006 // Find space required to bring the bottom of the selected item fully into view
1007 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
1008
1009 // Don't scroll more than half the height of the list
1010 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1011 int offset = Math.min(spaceAbove, spaceBelow);
1012 offset = Math.min(offset, halfVerticalSpace);
1013
1014 // We placed oldSel, so offset that item
1015 oldSel.offsetTopAndBottom(-offset);
1016 // Now offset the selected item to get it into view
1017 sel.offsetTopAndBottom(-offset);
1018 }
1019
1020 // Fill in views above and below
1021 if (!mStackFromBottom) {
1022 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1023 adjustViewsUpOrDown();
1024 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1025 } else {
1026 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1027 adjustViewsUpOrDown();
1028 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1029 }
1030 } else if (delta < 0) {
1031 /*
1032 * Case 2: Scrolling up.
1033 */
1034
1035 /*
1036 * Before After
1037 * | | | |
1038 * +-------+ +-------+
1039 * | A | | A |
1040 * +-------+ => | 1 |
1041 * | B | +-------+
1042 * | 2 | | B |
1043 * +-------+ +-------+
1044 * | | | |
1045 *
1046 * Try to keep the top of the item about to become selected where it was.
1047 * newSel = A
1048 * olSel = B
1049 */
1050
1051 if (newSel != null) {
1052 // Try to position the top of newSel (A) where it was before it was selected
1053 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1054 true);
1055 } else {
1056 // If (A) was not on screen and so did not have a view, position
1057 // it above the oldSel (B)
1058 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1059 true);
1060 }
1061
1062 // Some of the newly selected item extends above the top of the list
1063 if (sel.getTop() < topSelectionPixel) {
1064 // Find space required to bring the top of the selected item fully into view
1065 int spaceAbove = topSelectionPixel - sel.getTop();
1066
1067 // Find space available below the selection into which we can scroll downwards
1068 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1069
1070 // Don't scroll more than half the height of the list
1071 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1072 int offset = Math.min(spaceAbove, spaceBelow);
1073 offset = Math.min(offset, halfVerticalSpace);
1074
1075 // Offset the selected item to get it into view
1076 sel.offsetTopAndBottom(offset);
1077 }
1078
1079 // Fill in views above and below
1080 fillAboveAndBelow(sel, selectedPosition);
1081 } else {
1082
1083 int oldTop = oldSel.getTop();
1084
1085 /*
1086 * Case 3: Staying still
1087 */
1088 sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1089
1090 // We're staying still...
1091 if (oldTop < childrenTop) {
1092 // ... but the top of the old selection was off screen.
1093 // (This can happen if the data changes size out from under us)
1094 int newBottom = sel.getBottom();
1095 if (newBottom < childrenTop + 20) {
1096 // Not enough visible -- bring it onscreen
1097 sel.offsetTopAndBottom(childrenTop - sel.getTop());
1098 }
1099 }
1100
1101 // Fill in views above and below
1102 fillAboveAndBelow(sel, selectedPosition);
1103 }
1104
1105 return sel;
1106 }
1107
Adam Powell9bf3c122010-02-26 11:32:07 -08001108 private class FocusSelector implements Runnable {
1109 private int mPosition;
1110 private int mPositionTop;
1111
1112 public FocusSelector setup(int position, int top) {
1113 mPosition = position;
1114 mPositionTop = top;
1115 return this;
1116 }
1117
1118 public void run() {
1119 setSelectionFromTop(mPosition, mPositionTop);
1120 }
1121 }
1122
1123 @Override
1124 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1125 if (getChildCount() > 0) {
1126 View focusedChild = getFocusedChild();
1127 if (focusedChild != null) {
1128 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1129 final int childBottom = focusedChild.getBottom();
1130 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1131 final int top = focusedChild.getTop() - offset;
1132 if (mFocusSelector == null) {
1133 mFocusSelector = new FocusSelector();
1134 }
1135 post(mFocusSelector.setup(childPosition, top));
1136 }
1137 }
1138 super.onSizeChanged(w, h, oldw, oldh);
1139 }
1140
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 @Override
1142 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1143 // Sets up mListPadding
1144 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1145
Alan Viverette2ea32922015-06-26 13:31:50 -07001146 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1147 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1149 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1150
1151 int childWidth = 0;
1152 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001153 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154
1155 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
Alan Viverette2ea32922015-06-26 13:31:50 -07001156 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
1157 || heightMode == MeasureSpec.UNSPECIFIED)) {
Romain Guy21875052010-01-06 18:48:08 -08001158 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159
Alan Viverette2ea32922015-06-26 13:31:50 -07001160 // Lay out child directly against the parent measure spec so that
Adam Powellc55d5072014-12-18 15:22:26 -08001161 // we can obtain expected minimum width and height.
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001162 measureScrapChild(child, 0, widthMeasureSpec, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163
1164 childWidth = child.getMeasuredWidth();
1165 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001166 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001167
Romain Guy9c3184cc2010-02-25 17:32:54 -08001168 if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1169 ((LayoutParams) child.getLayoutParams()).viewType)) {
Alan Viverette4771b552014-08-15 18:02:23 -07001170 mRecycler.addScrapView(child, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 }
1172 }
1173
1174 if (widthMode == MeasureSpec.UNSPECIFIED) {
1175 widthSize = mListPadding.left + mListPadding.right + childWidth +
1176 getVerticalScrollbarWidth();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001177 } else {
Alan Viverette2ea32922015-06-26 13:31:50 -07001178 widthSize |= (childState & MEASURED_STATE_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 }
1180
1181 if (heightMode == MeasureSpec.UNSPECIFIED) {
1182 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1183 getVerticalFadingEdgeLength() * 2;
1184 }
1185
1186 if (heightMode == MeasureSpec.AT_MOST) {
1187 // TODO: after first layout we should maybe start at the first visible position, not 0
1188 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1189 }
1190
Alan Viverette2ea32922015-06-26 13:31:50 -07001191 setMeasuredDimension(widthSize, heightSize);
1192
1193 mWidthMeasureSpec = widthMeasureSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 }
1195
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001196 private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 LayoutParams p = (LayoutParams) child.getLayoutParams();
1198 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001199 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001200 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 }
1202 p.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04001203 p.isEnabled = mAdapter.isEnabled(position);
Romain Guy0bf88592010-03-02 13:38:44 -08001204 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205
Alan Viverette2ea32922015-06-26 13:31:50 -07001206 final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 mListPadding.left + mListPadding.right, p.width);
Alan Viverette2ea32922015-06-26 13:31:50 -07001208 final int lpHeight = p.height;
1209 final int childHeightSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210 if (lpHeight > 0) {
1211 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1212 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07001213 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(heightHint, MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 }
1215 child.measure(childWidthSpec, childHeightSpec);
Alan Viverette2ea32922015-06-26 13:31:50 -07001216
1217 // Since this view was measured directly aginst the parent measure
1218 // spec, we must measure it again before reuse.
1219 child.forceLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 }
1221
1222 /**
1223 * @return True to recycle the views used to measure this ListView in
1224 * UNSPECIFIED/AT_MOST modes, false otherwise.
1225 * @hide
1226 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001227 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 protected boolean recycleOnMeasure() {
1229 return true;
1230 }
1231
1232 /**
1233 * Measures the height of the given range of children (inclusive) and
1234 * returns the height with this ListView's padding and divider heights
1235 * included. If maxHeight is provided, the measuring will stop when the
1236 * current height reaches maxHeight.
1237 *
1238 * @param widthMeasureSpec The width measure spec to be given to a child's
1239 * {@link View#measure(int, int)}.
1240 * @param startPosition The position of the first child to be shown.
1241 * @param endPosition The (inclusive) position of the last child to be
1242 * shown. Specify {@link #NO_POSITION} if the last child should be
1243 * the last available child from the adapter.
1244 * @param maxHeight The maximum height that will be returned (if all the
1245 * children don't fit in this value, this value will be
1246 * returned).
1247 * @param disallowPartialChildPosition In general, whether the returned
1248 * height should only contain entire children. This is more
1249 * powerful--it is the first inclusive position at which partial
1250 * children will not be allowed. Example: it looks nice to have
1251 * at least 3 completely visible children, and in portrait this
1252 * will most likely fit; but in landscape there could be times
1253 * when even 2 children can not be completely shown, so a value
1254 * of 2 (remember, inclusive) would be good (assuming
1255 * startPosition is 0).
1256 * @return The height of this ListView with the given children.
1257 */
1258 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
Alan Viverette2ea32922015-06-26 13:31:50 -07001259 int maxHeight, int disallowPartialChildPosition) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 final ListAdapter adapter = mAdapter;
1261 if (adapter == null) {
1262 return mListPadding.top + mListPadding.bottom;
1263 }
1264
1265 // Include the padding of the list
1266 int returnedHeight = mListPadding.top + mListPadding.bottom;
1267 final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
1268 // The previous height value that was less than maxHeight and contained
1269 // no partial children
1270 int prevHeightWithoutPartialChild = 0;
1271 int i;
1272 View child;
1273
1274 // mItemCount - 1 since endPosition parameter is inclusive
1275 endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1276 final AbsListView.RecycleBin recycleBin = mRecycler;
1277 final boolean recyle = recycleOnMeasure();
Romain Guy21875052010-01-06 18:48:08 -08001278 final boolean[] isScrap = mIsScrap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279
1280 for (i = startPosition; i <= endPosition; ++i) {
Romain Guy21875052010-01-06 18:48:08 -08001281 child = obtainView(i, isScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001283 measureScrapChild(child, i, widthMeasureSpec, maxHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284
1285 if (i > 0) {
1286 // Count the divider for all but one child
1287 returnedHeight += dividerHeight;
1288 }
1289
1290 // Recycle the view before we possibly return from the method
Romain Guy9c3184cc2010-02-25 17:32:54 -08001291 if (recyle && recycleBin.shouldRecycleViewType(
1292 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001293 recycleBin.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294 }
1295
1296 returnedHeight += child.getMeasuredHeight();
1297
1298 if (returnedHeight >= maxHeight) {
1299 // We went over, figure out which height to return. If returnedHeight > maxHeight,
1300 // then the i'th position did not fit completely.
1301 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1302 && (i > disallowPartialChildPosition) // We've past the min pos
1303 && (prevHeightWithoutPartialChild > 0) // We have a prev height
1304 && (returnedHeight != maxHeight) // i'th child did not fit completely
1305 ? prevHeightWithoutPartialChild
1306 : maxHeight;
1307 }
1308
1309 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1310 prevHeightWithoutPartialChild = returnedHeight;
1311 }
1312 }
1313
1314 // At this point, we went through the range of children, and they each
1315 // completely fit, so return the returnedHeight
1316 return returnedHeight;
1317 }
1318
1319 @Override
1320 int findMotionRow(int y) {
1321 int childCount = getChildCount();
1322 if (childCount > 0) {
Adam Powell84222e02010-03-11 13:42:57 -08001323 if (!mStackFromBottom) {
1324 for (int i = 0; i < childCount; i++) {
1325 View v = getChildAt(i);
1326 if (y <= v.getBottom()) {
1327 return mFirstPosition + i;
1328 }
1329 }
1330 } else {
1331 for (int i = childCount - 1; i >= 0; i--) {
1332 View v = getChildAt(i);
1333 if (y >= v.getTop()) {
1334 return mFirstPosition + i;
1335 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 }
1337 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001338 }
1339 return INVALID_POSITION;
1340 }
1341
1342 /**
1343 * Put a specific item at a specific location on the screen and then build
1344 * up and down from there.
1345 *
1346 * @param position The reference view to use as the starting point
1347 * @param top Pixel offset from the top of this view to the top of the
1348 * reference view.
1349 *
1350 * @return The selected view, or null if the selected view is outside the
1351 * visible area.
1352 */
1353 private View fillSpecific(int position, int top) {
1354 boolean tempIsSelected = position == mSelectedPosition;
1355 View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1356 // Possibly changed again in fillUp if we add rows above this one.
1357 mFirstPosition = position;
1358
1359 View above;
1360 View below;
1361
1362 final int dividerHeight = mDividerHeight;
1363 if (!mStackFromBottom) {
1364 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1365 // This will correct for the top of the first view not touching the top of the list
1366 adjustViewsUpOrDown();
1367 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1368 int childCount = getChildCount();
1369 if (childCount > 0) {
1370 correctTooHigh(childCount);
1371 }
1372 } else {
1373 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1374 // This will correct for the bottom of the last view not touching the bottom of the list
1375 adjustViewsUpOrDown();
1376 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1377 int childCount = getChildCount();
1378 if (childCount > 0) {
1379 correctTooLow(childCount);
1380 }
1381 }
1382
1383 if (tempIsSelected) {
1384 return temp;
1385 } else if (above != null) {
1386 return above;
1387 } else {
1388 return below;
1389 }
1390 }
1391
1392 /**
1393 * Check if we have dragged the bottom of the list too high (we have pushed the
1394 * top element off the top of the screen when we did not need to). Correct by sliding
1395 * everything back down.
1396 *
1397 * @param childCount Number of children
1398 */
1399 private void correctTooHigh(int childCount) {
1400 // First see if the last item is visible. If it is not, it is OK for the
1401 // top of the list to be pushed up.
1402 int lastPosition = mFirstPosition + childCount - 1;
1403 if (lastPosition == mItemCount - 1 && childCount > 0) {
1404
1405 // Get the last child ...
1406 final View lastChild = getChildAt(childCount - 1);
1407
1408 // ... and its bottom edge
1409 final int lastBottom = lastChild.getBottom();
1410
1411 // This is bottom of our drawable area
1412 final int end = (mBottom - mTop) - mListPadding.bottom;
1413
1414 // This is how far the bottom edge of the last view is from the bottom of the
1415 // drawable area
1416 int bottomOffset = end - lastBottom;
1417 View firstChild = getChildAt(0);
1418 final int firstTop = firstChild.getTop();
1419
1420 // Make sure we are 1) Too high, and 2) Either there are more rows above the
1421 // first row or the first row is scrolled off the top of the drawable area
1422 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
1423 if (mFirstPosition == 0) {
1424 // Don't pull the top too far down
1425 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1426 }
1427 // Move everything down
1428 offsetChildrenTopAndBottom(bottomOffset);
1429 if (mFirstPosition > 0) {
1430 // Fill the gap that was opened above mFirstPosition with more rows, if
1431 // possible
1432 fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1433 // Close up the remaining gap
1434 adjustViewsUpOrDown();
1435 }
1436
1437 }
1438 }
1439 }
1440
1441 /**
1442 * Check if we have dragged the bottom of the list too low (we have pushed the
1443 * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1444 * everything back up.
1445 *
1446 * @param childCount Number of children
1447 */
1448 private void correctTooLow(int childCount) {
1449 // First see if the first item is visible. If it is not, it is OK for the
1450 // bottom of the list to be pushed down.
1451 if (mFirstPosition == 0 && childCount > 0) {
1452
1453 // Get the first child ...
1454 final View firstChild = getChildAt(0);
1455
1456 // ... and its top edge
1457 final int firstTop = firstChild.getTop();
1458
1459 // This is top of our drawable area
1460 final int start = mListPadding.top;
1461
1462 // This is bottom of our drawable area
1463 final int end = (mBottom - mTop) - mListPadding.bottom;
1464
1465 // This is how far the top edge of the first view is from the top of the
1466 // drawable area
1467 int topOffset = firstTop - start;
1468 View lastChild = getChildAt(childCount - 1);
1469 final int lastBottom = lastChild.getBottom();
1470 int lastPosition = mFirstPosition + childCount - 1;
1471
1472 // Make sure we are 1) Too low, and 2) Either there are more rows below the
1473 // last row or the last row is scrolled off the bottom of the drawable area
Romain Guy6198ae82009-08-31 17:45:55 -07001474 if (topOffset > 0) {
1475 if (lastPosition < mItemCount - 1 || lastBottom > end) {
1476 if (lastPosition == mItemCount - 1) {
1477 // Don't pull the bottom too far up
1478 topOffset = Math.min(topOffset, lastBottom - end);
1479 }
1480 // Move everything up
1481 offsetChildrenTopAndBottom(-topOffset);
1482 if (lastPosition < mItemCount - 1) {
1483 // Fill the gap that was opened below the last position with more rows, if
1484 // possible
1485 fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1486 // Close up the remaining gap
1487 adjustViewsUpOrDown();
1488 }
1489 } else if (lastPosition == mItemCount - 1) {
1490 adjustViewsUpOrDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 }
1492 }
1493 }
1494 }
1495
1496 @Override
1497 protected void layoutChildren() {
1498 final boolean blockLayoutRequests = mBlockLayoutRequests;
Alan Viveretted44696c2013-07-18 10:37:15 -07001499 if (blockLayoutRequests) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001500 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 }
1502
Alan Viveretted44696c2013-07-18 10:37:15 -07001503 mBlockLayoutRequests = true;
1504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 try {
1506 super.layoutChildren();
1507
1508 invalidate();
1509
1510 if (mAdapter == null) {
1511 resetList();
1512 invokeOnItemScrollListener();
1513 return;
1514 }
1515
Alan Viveretted44696c2013-07-18 10:37:15 -07001516 final int childrenTop = mListPadding.top;
1517 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1518 final int childCount = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519
Romain Guyead0d4d2009-12-08 17:33:53 -08001520 int index = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 int delta = 0;
1522
1523 View sel;
1524 View oldSel = null;
1525 View oldFirst = null;
1526 View newSel = null;
1527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528 // Remember stuff we will need down below
1529 switch (mLayoutMode) {
1530 case LAYOUT_SET_SELECTION:
1531 index = mNextSelectedPosition - mFirstPosition;
1532 if (index >= 0 && index < childCount) {
1533 newSel = getChildAt(index);
1534 }
1535 break;
1536 case LAYOUT_FORCE_TOP:
1537 case LAYOUT_FORCE_BOTTOM:
1538 case LAYOUT_SPECIFIC:
1539 case LAYOUT_SYNC:
1540 break;
1541 case LAYOUT_MOVE_SELECTION:
1542 default:
1543 // Remember the previously selected view
1544 index = mSelectedPosition - mFirstPosition;
1545 if (index >= 0 && index < childCount) {
1546 oldSel = getChildAt(index);
1547 }
1548
1549 // Remember the previous first child
1550 oldFirst = getChildAt(0);
1551
1552 if (mNextSelectedPosition >= 0) {
1553 delta = mNextSelectedPosition - mSelectedPosition;
1554 }
1555
1556 // Caution: newSel might be null
1557 newSel = getChildAt(index + delta);
1558 }
1559
1560
1561 boolean dataChanged = mDataChanged;
1562 if (dataChanged) {
1563 handleDataChanged();
1564 }
1565
1566 // Handle the empty set by removing all views that are visible
1567 // and calling it a day
1568 if (mItemCount == 0) {
1569 resetList();
1570 invokeOnItemScrollListener();
1571 return;
Romain Guyb45f1242009-03-24 21:30:00 -07001572 } else if (mItemCount != mAdapter.getCount()) {
1573 throw new IllegalStateException("The content of the adapter has changed but "
1574 + "ListView did not receive a notification. Make sure the content of "
Alan Viverette7d8314d2013-09-10 11:22:53 -07001575 + "your adapter is not modified from a background thread, but only from "
1576 + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
1577 + "when its content changes. [in ListView(" + getId() + ", " + getClass()
Owen Lin3940f2d2009-08-13 15:21:16 +08001578 + ") with Adapter(" + mAdapter.getClass() + ")]");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 }
1580
1581 setSelectedPositionInt(mNextSelectedPosition);
1582
Alan Viverette3e141622014-02-18 17:05:13 -08001583 AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
1584 View accessibilityFocusLayoutRestoreView = null;
1585 int accessibilityFocusPosition = INVALID_POSITION;
1586
1587 // Remember which child, if any, had accessibility focus. This must
1588 // occur before recycling any views, since that will clear
1589 // accessibility focus.
1590 final ViewRootImpl viewRootImpl = getViewRootImpl();
1591 if (viewRootImpl != null) {
1592 final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
1593 if (focusHost != null) {
1594 final View focusChild = getAccessibilityFocusedChild(focusHost);
1595 if (focusChild != null) {
1596 if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
1597 || focusChild.hasTransientState() || mAdapterHasStableIds) {
1598 // The views won't be changing, so try to maintain
1599 // focus on the current host and virtual view.
1600 accessibilityFocusLayoutRestoreView = focusHost;
1601 accessibilityFocusLayoutRestoreNode = viewRootImpl
1602 .getAccessibilityFocusedVirtualView();
1603 }
1604
1605 // If all else fails, maintain focus at the same
1606 // position.
1607 accessibilityFocusPosition = getPositionForView(focusChild);
1608 }
1609 }
Alan Viveretteb53c5f62013-03-15 15:50:37 -07001610 }
1611
Alan Viverette3e141622014-02-18 17:05:13 -08001612 View focusLayoutRestoreDirectChild = null;
1613 View focusLayoutRestoreView = null;
1614
1615 // Take focus back to us temporarily to avoid the eventual call to
1616 // clear focus when removing the focused child below from messing
1617 // things up when ViewAncestor assigns focus back to someone else.
Alan Viveretted44696c2013-07-18 10:37:15 -07001618 final View focusedChild = getFocusedChild();
1619 if (focusedChild != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001620 // TODO: in some cases focusedChild.getParent() == null
1621
1622 // We can remember the focused view to restore after re-layout
1623 // if the data hasn't changed, or if the focused position is a
1624 // header or footer.
Alan Viverette2b460d02015-07-06 11:01:50 -07001625 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
1626 || focusedChild.hasTransientState() || mAdapterHasStableIds) {
Alan Viverette3e141622014-02-18 17:05:13 -08001627 focusLayoutRestoreDirectChild = focusedChild;
1628 // Remember the specific view that had focus.
1629 focusLayoutRestoreView = findFocus();
1630 if (focusLayoutRestoreView != null) {
1631 // Tell it we are going to mess with it.
1632 focusLayoutRestoreView.onStartTemporaryDetach();
1633 }
1634 }
1635 requestFocus();
Alan Viveretted44696c2013-07-18 10:37:15 -07001636 }
1637
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001638 // Pull all children into the RecycleBin.
1639 // These views will be reused if possible
1640 final int firstPosition = mFirstPosition;
1641 final RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001642 if (dataChanged) {
1643 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001644 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001645 }
1646 } else {
1647 recycleBin.fillActiveViews(childCount, firstPosition);
1648 }
1649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001650 // Clear out old views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001651 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001652 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001653
1654 switch (mLayoutMode) {
1655 case LAYOUT_SET_SELECTION:
1656 if (newSel != null) {
1657 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1658 } else {
1659 sel = fillFromMiddle(childrenTop, childrenBottom);
1660 }
1661 break;
1662 case LAYOUT_SYNC:
1663 sel = fillSpecific(mSyncPosition, mSpecificTop);
1664 break;
1665 case LAYOUT_FORCE_BOTTOM:
1666 sel = fillUp(mItemCount - 1, childrenBottom);
1667 adjustViewsUpOrDown();
1668 break;
1669 case LAYOUT_FORCE_TOP:
1670 mFirstPosition = 0;
1671 sel = fillFromTop(childrenTop);
1672 adjustViewsUpOrDown();
1673 break;
1674 case LAYOUT_SPECIFIC:
1675 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
1676 break;
1677 case LAYOUT_MOVE_SELECTION:
1678 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1679 break;
1680 default:
1681 if (childCount == 0) {
1682 if (!mStackFromBottom) {
1683 final int position = lookForSelectablePosition(0, true);
1684 setSelectedPositionInt(position);
1685 sel = fillFromTop(childrenTop);
1686 } else {
1687 final int position = lookForSelectablePosition(mItemCount - 1, false);
1688 setSelectedPositionInt(position);
1689 sel = fillUp(mItemCount - 1, childrenBottom);
1690 }
1691 } else {
1692 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1693 sel = fillSpecific(mSelectedPosition,
1694 oldSel == null ? childrenTop : oldSel.getTop());
1695 } else if (mFirstPosition < mItemCount) {
1696 sel = fillSpecific(mFirstPosition,
1697 oldFirst == null ? childrenTop : oldFirst.getTop());
1698 } else {
1699 sel = fillSpecific(0, childrenTop);
1700 }
1701 }
1702 break;
1703 }
1704
1705 // Flush any cached views that did not get reused above
1706 recycleBin.scrapActiveViews();
1707
1708 if (sel != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001709 // The current selected item should get focus if items are
1710 // focusable.
1711 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1712 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1713 focusLayoutRestoreView != null &&
1714 focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1715 if (!focusWasTaken) {
1716 // Selected item didn't take focus, but we still want to
1717 // make sure something else outside of the selected view
1718 // has focus.
Romain Guy3616a412009-09-15 13:50:37 -07001719 final View focused = getFocusedChild();
1720 if (focused != null) {
1721 focused.clearFocus();
1722 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001723 positionSelector(INVALID_POSITION, sel);
Alan Viverette3e141622014-02-18 17:05:13 -08001724 } else {
1725 sel.setSelected(false);
1726 mSelectorRect.setEmpty();
Romain Guy3616a412009-09-15 13:50:37 -07001727 }
1728 } else {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001729 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001730 }
1731 mSelectedTop = sel.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001732 } else {
Alan Viverettee3c433a2014-06-19 12:48:45 -07001733 final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
1734 || mTouchMode == TOUCH_MODE_DONE_WAITING;
1735 if (inTouchMode) {
1736 // If the user's finger is down, select the motion position.
Alan Viveretted44696c2013-07-18 10:37:15 -07001737 final View child = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee3c433a2014-06-19 12:48:45 -07001738 if (child != null) {
Alan Viveretted44696c2013-07-18 10:37:15 -07001739 positionSelector(mMotionPosition, child);
1740 }
Alan Viverettee3c433a2014-06-19 12:48:45 -07001741 } else if (mSelectorPosition != INVALID_POSITION) {
1742 // If we had previously positioned the selector somewhere,
1743 // put it back there. It might not match up with the data,
1744 // but it's transitioning out so it's not a big deal.
1745 final View child = getChildAt(mSelectorPosition - mFirstPosition);
1746 if (child != null) {
1747 positionSelector(mSelectorPosition, child);
1748 }
Romain Guy3616a412009-09-15 13:50:37 -07001749 } else {
Alan Viverettee3c433a2014-06-19 12:48:45 -07001750 // Otherwise, clear selection.
Romain Guy3616a412009-09-15 13:50:37 -07001751 mSelectedTop = 0;
1752 mSelectorRect.setEmpty();
1753 }
Alan Viverette3e141622014-02-18 17:05:13 -08001754
1755 // Even if there is not selected position, we may need to
1756 // restore focus (i.e. something focusable in touch mode).
1757 if (hasFocus() && focusLayoutRestoreView != null) {
1758 focusLayoutRestoreView.requestFocus();
1759 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001760 }
1761
Alan Viverette3e141622014-02-18 17:05:13 -08001762 // Attempt to restore accessibility focus, if necessary.
Alan Viverette2e6fc8c2014-02-24 11:09:18 -08001763 if (viewRootImpl != null) {
1764 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1765 if (newAccessibilityFocusedView == null) {
1766 if (accessibilityFocusLayoutRestoreView != null
1767 && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
1768 final AccessibilityNodeProvider provider =
1769 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1770 if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
1771 final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1772 accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1773 provider.performAction(virtualViewId,
1774 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1775 } else {
1776 accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1777 }
1778 } else if (accessibilityFocusPosition != INVALID_POSITION) {
1779 // Bound the position within the visible children.
1780 final int position = MathUtils.constrain(
1781 accessibilityFocusPosition - mFirstPosition, 0,
1782 getChildCount() - 1);
1783 final View restoreView = getChildAt(position);
1784 if (restoreView != null) {
1785 restoreView.requestAccessibilityFocus();
1786 }
Alan Viverette68207512013-08-22 12:33:07 -07001787 }
alanv30ee76c2012-09-07 10:31:16 -07001788 }
1789 }
1790
Alan Viverette3e141622014-02-18 17:05:13 -08001791 // Tell focus view we are done mucking with it, if it is still in
1792 // our view hierarchy.
1793 if (focusLayoutRestoreView != null
1794 && focusLayoutRestoreView.getWindowToken() != null) {
1795 focusLayoutRestoreView.onFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001796 }
1797
1798 mLayoutMode = LAYOUT_NORMAL;
1799 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001800 if (mPositionScrollAfterLayout != null) {
1801 post(mPositionScrollAfterLayout);
1802 mPositionScrollAfterLayout = null;
1803 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001804 mNeedSync = false;
1805 setNextSelectedPositionInt(mSelectedPosition);
1806
1807 updateScrollIndicators();
1808
1809 if (mItemCount > 0) {
1810 checkSelectionChanged();
1811 }
1812
1813 invokeOnItemScrollListener();
1814 } finally {
1815 if (!blockLayoutRequests) {
1816 mBlockLayoutRequests = false;
1817 }
1818 }
1819 }
1820
1821 /**
Alan Viverette3e141622014-02-18 17:05:13 -08001822 * @param child a direct child of this list.
1823 * @return Whether child is a header or footer view.
1824 */
1825 private boolean isDirectChildHeaderOrFooter(View child) {
1826 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
1827 final int numHeaders = headers.size();
1828 for (int i = 0; i < numHeaders; i++) {
1829 if (child == headers.get(i).view) {
1830 return true;
1831 }
1832 }
1833
1834 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
1835 final int numFooters = footers.size();
1836 for (int i = 0; i < numFooters; i++) {
1837 if (child == footers.get(i).view) {
1838 return true;
1839 }
1840 }
1841
1842 return false;
1843 }
1844
1845 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001846 * Obtain the view and add it to our list of children. The view can be made
1847 * fresh, converted from an unused view, or used as is if it was in the
1848 * recycle bin.
1849 *
1850 * @param position Logical position in the list
1851 * @param y Top or bottom edge of the view to add
1852 * @param flow If flow is true, align top edge to y. If false, align bottom
1853 * edge to y.
1854 * @param childrenLeft Left edge where children should be positioned
1855 * @param selected Is this position selected?
1856 * @return View that was added
1857 */
1858 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
1859 boolean selected) {
1860 View child;
1861
1862
1863 if (!mDataChanged) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001864 // Try to use an existing view for this position
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001865 child = mRecycler.getActiveView(position);
1866 if (child != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001867 // Found it -- we're using an existing child
1868 // This just needs to be positioned
1869 setupChild(child, position, y, flow, childrenLeft, selected, true);
1870
1871 return child;
1872 }
1873 }
1874
1875 // Make a new view for this position, or convert an unused view if possible
Romain Guy21875052010-01-06 18:48:08 -08001876 child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001877
1878 // This needs to be positioned and measured
Romain Guy21875052010-01-06 18:48:08 -08001879 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001880
1881 return child;
1882 }
1883
1884 /**
1885 * Add a view as a child and make sure it is measured (if necessary) and
1886 * positioned properly.
1887 *
1888 * @param child The view to add
1889 * @param position The position of this child
1890 * @param y The y position relative to which this view will be positioned
1891 * @param flowDown If true, align top edge to y. If false, align bottom
1892 * edge to y.
1893 * @param childrenLeft Left edge where children should be positioned
1894 * @param selected Is this position selected?
1895 * @param recycled Has this view been pulled from the recycle bin? If so it
1896 * does not need to be remeasured.
1897 */
1898 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
1899 boolean selected, boolean recycled) {
Romain Guy5fade8c2013-07-10 16:36:18 -07001900 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
1901
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001902 final boolean isSelected = selected && shouldShowSelector();
1903 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07001904 final int mode = mTouchMode;
1905 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
1906 mMotionPosition == position;
1907 final boolean updateChildPressed = isPressed != child.isPressed();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001908 final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
1909
1910 // Respect layout params that are already in the view. Otherwise make some up...
1911 // noinspection unchecked
1912 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
1913 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001914 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001915 }
1916 p.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04001917 p.isEnabled = mAdapter.isEnabled(position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001918
Alan Viverette2ea32922015-06-26 13:31:50 -07001919 if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
1920 && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001921 attachViewToParent(child, flowDown ? -1 : 0, p);
1922 } else {
Romain Guy0bf88592010-03-02 13:38:44 -08001923 p.forceAdd = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001924 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
1925 p.recycledHeaderFooter = true;
1926 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001927 addViewInLayout(child, flowDown ? -1 : 0, p, true);
1928 }
1929
1930 if (updateChildSelected) {
1931 child.setSelected(isSelected);
1932 }
1933
Romain Guy3616a412009-09-15 13:50:37 -07001934 if (updateChildPressed) {
1935 child.setPressed(isPressed);
1936 }
1937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
1939 if (child instanceof Checkable) {
1940 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07001941 } else if (getContext().getApplicationInfo().targetSdkVersion
1942 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
1943 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001944 }
1945 }
1946
1947 if (needToMeasure) {
Alan Viverette2ea32922015-06-26 13:31:50 -07001948 final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001949 mListPadding.left + mListPadding.right, p.width);
Alan Viverette2ea32922015-06-26 13:31:50 -07001950 final int lpHeight = p.height;
1951 final int childHeightSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001952 if (lpHeight > 0) {
1953 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1954 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07001955 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001956 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001957 }
1958 child.measure(childWidthSpec, childHeightSpec);
1959 } else {
1960 cleanupLayoutState(child);
1961 }
1962
1963 final int w = child.getMeasuredWidth();
1964 final int h = child.getMeasuredHeight();
1965 final int childTop = flowDown ? y : y - h;
1966
1967 if (needToMeasure) {
1968 final int childRight = childrenLeft + w;
1969 final int childBottom = childTop + h;
1970 child.layout(childrenLeft, childTop, childRight, childBottom);
1971 } else {
1972 child.offsetLeftAndRight(childrenLeft - child.getLeft());
1973 child.offsetTopAndBottom(childTop - child.getTop());
1974 }
1975
1976 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
1977 child.setDrawingCacheEnabled(true);
1978 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001979
1980 if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
1981 != position) {
1982 child.jumpDrawablesToCurrentState();
1983 }
Romain Guy5fade8c2013-07-10 16:36:18 -07001984
1985 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001986 }
1987
1988 @Override
1989 protected boolean canAnimate() {
1990 return super.canAnimate() && mItemCount > 0;
1991 }
1992
1993 /**
1994 * Sets the currently selected item. If in touch mode, the item will not be selected
1995 * but it will still be positioned appropriately. If the specified selection position
1996 * is less than 0, then the item at position 0 will be selected.
1997 *
1998 * @param position Index (starting at 0) of the data item to be selected.
1999 */
2000 @Override
2001 public void setSelection(int position) {
2002 setSelectionFromTop(position, 0);
2003 }
2004
2005 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002006 * Makes the item at the supplied position selected.
Mike Cleronf116bf82009-09-27 19:14:12 -07002007 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002008 * @param position the position of the item to select
2009 */
2010 @Override
2011 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002012 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07002013 boolean awakeScrollbars = false;
2014
2015 final int selectedPosition = mSelectedPosition;
2016
2017 if (selectedPosition >= 0) {
2018 if (position == selectedPosition - 1) {
2019 awakeScrollbars = true;
2020 } else if (position == selectedPosition + 1) {
2021 awakeScrollbars = true;
2022 }
2023 }
2024
Adam Powell1fa179ef2012-04-12 15:01:40 -07002025 if (mPositionScroller != null) {
2026 mPositionScroller.stop();
2027 }
2028
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002029 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07002030
2031 if (awakeScrollbars) {
2032 awakenScrollBars();
2033 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002034 }
2035
2036 /**
2037 * Find a position that can be selected (i.e., is not a separator).
2038 *
2039 * @param position The starting position to look at.
2040 * @param lookDown Whether to look down for other positions.
2041 * @return The next selectable position starting at position and then searching either up or
2042 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
2043 */
2044 @Override
2045 int lookForSelectablePosition(int position, boolean lookDown) {
2046 final ListAdapter adapter = mAdapter;
2047 if (adapter == null || isInTouchMode()) {
2048 return INVALID_POSITION;
2049 }
2050
2051 final int count = adapter.getCount();
2052 if (!mAreAllItemsSelectable) {
2053 if (lookDown) {
2054 position = Math.max(0, position);
2055 while (position < count && !adapter.isEnabled(position)) {
2056 position++;
2057 }
2058 } else {
2059 position = Math.min(position, count - 1);
2060 while (position >= 0 && !adapter.isEnabled(position)) {
2061 position--;
2062 }
2063 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002064 }
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002065
2066 if (position < 0 || position >= count) {
2067 return INVALID_POSITION;
2068 }
2069
2070 return position;
2071 }
2072
2073 /**
2074 * Find a position that can be selected (i.e., is not a separator). If there
2075 * are no selectable positions in the specified direction from the starting
2076 * position, searches in the opposite direction from the starting position
2077 * to the current position.
2078 *
2079 * @param current the current position
2080 * @param position the starting position
2081 * @param lookDown whether to look down for other positions
2082 * @return the next selectable position, or {@link #INVALID_POSITION} if
2083 * nothing can be found
2084 */
2085 int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2086 final ListAdapter adapter = mAdapter;
2087 if (adapter == null || isInTouchMode()) {
2088 return INVALID_POSITION;
2089 }
2090
2091 // First check after the starting position in the specified direction.
2092 final int after = lookForSelectablePosition(position, lookDown);
2093 if (after != INVALID_POSITION) {
2094 return after;
2095 }
2096
2097 // Then check between the starting position and the current position.
2098 final int count = adapter.getCount();
2099 current = MathUtils.constrain(current, -1, count - 1);
2100 if (lookDown) {
2101 position = Math.min(position - 1, count - 1);
2102 while ((position > current) && !adapter.isEnabled(position)) {
2103 position--;
2104 }
2105 if (position <= current) {
2106 return INVALID_POSITION;
2107 }
2108 } else {
2109 position = Math.max(0, position + 1);
2110 while ((position < current) && !adapter.isEnabled(position)) {
2111 position++;
2112 }
2113 if (position >= current) {
2114 return INVALID_POSITION;
2115 }
2116 }
2117
2118 return position;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002119 }
2120
2121 /**
2122 * setSelectionAfterHeaderView set the selection to be the first list item
2123 * after the header views.
2124 */
2125 public void setSelectionAfterHeaderView() {
2126 final int count = mHeaderViewInfos.size();
2127 if (count > 0) {
2128 mNextSelectedPosition = 0;
2129 return;
2130 }
2131
2132 if (mAdapter != null) {
2133 setSelection(count);
2134 } else {
2135 mNextSelectedPosition = count;
2136 mLayoutMode = LAYOUT_SET_SELECTION;
2137 }
2138
2139 }
2140
2141 @Override
2142 public boolean dispatchKeyEvent(KeyEvent event) {
2143 // Dispatch in the normal way
2144 boolean handled = super.dispatchKeyEvent(event);
2145 if (!handled) {
2146 // If we didn't handle it...
2147 View focused = getFocusedChild();
2148 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2149 // ... and our focused child didn't handle it
2150 // ... give it to ourselves so we can scroll if necessary
2151 handled = onKeyDown(event.getKeyCode(), event);
2152 }
2153 }
2154 return handled;
2155 }
2156
2157 @Override
2158 public boolean onKeyDown(int keyCode, KeyEvent event) {
2159 return commonKey(keyCode, 1, event);
2160 }
2161
2162 @Override
2163 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2164 return commonKey(keyCode, repeatCount, event);
2165 }
2166
2167 @Override
2168 public boolean onKeyUp(int keyCode, KeyEvent event) {
2169 return commonKey(keyCode, 1, event);
2170 }
2171
2172 private boolean commonKey(int keyCode, int count, KeyEvent event) {
Adam Powell31986b52013-09-24 14:53:30 -07002173 if (mAdapter == null || !isAttachedToWindow()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002174 return false;
2175 }
2176
2177 if (mDataChanged) {
2178 layoutChildren();
2179 }
2180
2181 boolean handled = false;
2182 int action = event.getAction();
2183
2184 if (action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002185 switch (keyCode) {
2186 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002187 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002188 handled = resurrectSelectionIfNeeded();
2189 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002190 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002191 if (arrowScroll(FOCUS_UP)) {
2192 handled = true;
2193 } else {
2194 break;
2195 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002196 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002197 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002198 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002199 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200 }
2201 break;
2202
2203 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002204 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002205 handled = resurrectSelectionIfNeeded();
2206 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002207 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002208 if (arrowScroll(FOCUS_DOWN)) {
2209 handled = true;
2210 } else {
2211 break;
2212 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002213 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002214 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002215 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002216 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002217 }
2218 break;
2219
2220 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002221 if (event.hasNoModifiers()) {
2222 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002224 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002226 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002227 if (event.hasNoModifiers()) {
2228 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002230 break;
2231
2232 case KeyEvent.KEYCODE_DPAD_CENTER:
2233 case KeyEvent.KEYCODE_ENTER:
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002234 if (event.hasNoModifiers()) {
2235 handled = resurrectSelectionIfNeeded();
2236 if (!handled
2237 && event.getRepeatCount() == 0 && getChildCount() > 0) {
2238 keyPressed();
2239 handled = true;
2240 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002241 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002242 break;
2243
2244 case KeyEvent.KEYCODE_SPACE:
2245 if (mPopup == null || !mPopup.isShowing()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002246 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002247 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002248 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002249 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002250 }
2251 handled = true;
2252 }
2253 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002254
2255 case KeyEvent.KEYCODE_PAGE_UP:
2256 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002257 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002258 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
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_PAGE_DOWN:
2264 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002265 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002266 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002267 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002268 }
2269 break;
2270
2271 case KeyEvent.KEYCODE_MOVE_HOME:
2272 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002273 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002274 }
2275 break;
2276
2277 case KeyEvent.KEYCODE_MOVE_END:
2278 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002279 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002280 }
2281 break;
2282
2283 case KeyEvent.KEYCODE_TAB:
Alan Viverette760a2d82015-08-17 14:31:42 -04002284 // This creates an asymmetry in TAB navigation order. At some
2285 // point in the future we may decide that it's preferable to
2286 // force the list selection to the top or bottom when receiving
2287 // TAB focus from another widget, but for now this is adequate.
2288 if (event.hasNoModifiers()) {
2289 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2290 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2291 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002292 }
2293 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002294 }
2295 }
2296
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002297 if (handled) {
2298 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002299 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002300
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002301 if (sendToTextFilter(keyCode, count, event)) {
2302 return true;
2303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002304
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002305 switch (action) {
2306 case KeyEvent.ACTION_DOWN:
2307 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002308
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002309 case KeyEvent.ACTION_UP:
2310 return super.onKeyUp(keyCode, event);
2311
2312 case KeyEvent.ACTION_MULTIPLE:
2313 return super.onKeyMultiple(keyCode, count, event);
2314
2315 default: // shouldn't happen
2316 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002317 }
2318 }
2319
2320 /**
2321 * Scrolls up or down by the number of items currently present on screen.
2322 *
2323 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2324 * @return whether selection was moved
2325 */
2326 boolean pageScroll(int direction) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002327 final int nextPage;
2328 final boolean down;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002329
2330 if (direction == FOCUS_UP) {
2331 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002332 down = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002333 } else if (direction == FOCUS_DOWN) {
2334 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2335 down = true;
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002336 } else {
2337 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002338 }
2339
2340 if (nextPage >= 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002341 final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002342 if (position >= 0) {
2343 mLayoutMode = LAYOUT_SPECIFIC;
2344 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2345
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002346 if (down && (position > (mItemCount - getChildCount()))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002347 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2348 }
2349
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002350 if (!down && (position < getChildCount())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002351 mLayoutMode = LAYOUT_FORCE_TOP;
2352 }
2353
2354 setSelectionInt(position);
2355 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002356 if (!awakenScrollBars()) {
2357 invalidate();
2358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002359
2360 return true;
2361 }
2362 }
2363
2364 return false;
2365 }
2366
2367 /**
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002368 * Go to the last or first item if possible (not worrying about panning
2369 * across or navigating within the internal focus of the currently selected
2370 * item.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002371 *
2372 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002373 * @return whether selection was moved
2374 */
2375 boolean fullScroll(int direction) {
2376 boolean moved = false;
2377 if (direction == FOCUS_UP) {
2378 if (mSelectedPosition != 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002379 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002380 if (position >= 0) {
2381 mLayoutMode = LAYOUT_FORCE_TOP;
2382 setSelectionInt(position);
2383 invokeOnItemScrollListener();
2384 }
2385 moved = true;
2386 }
2387 } else if (direction == FOCUS_DOWN) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002388 final int lastItem = (mItemCount - 1);
2389 if (mSelectedPosition < lastItem) {
2390 final int position = lookForSelectablePositionAfter(
2391 mSelectedPosition, lastItem, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002392 if (position >= 0) {
2393 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2394 setSelectionInt(position);
2395 invokeOnItemScrollListener();
2396 }
2397 moved = true;
2398 }
2399 }
2400
Mike Cleronf116bf82009-09-27 19:14:12 -07002401 if (moved && !awakenScrollBars()) {
2402 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002403 invalidate();
2404 }
2405
2406 return moved;
2407 }
2408
2409 /**
2410 * To avoid horizontal focus searches changing the selected item, we
2411 * manually focus search within the selected item (as applicable), and
2412 * prevent focus from jumping to something within another item.
2413 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2414 * @return Whether this consumes the key event.
2415 */
2416 private boolean handleHorizontalFocusWithinListItem(int direction) {
2417 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002418 throw new IllegalArgumentException("direction must be one of"
2419 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002420 }
2421
2422 final int numChildren = getChildCount();
2423 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2424 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002425 if (selectedView != null && selectedView.hasFocus() &&
2426 selectedView instanceof ViewGroup) {
2427
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002428 final View currentFocus = selectedView.findFocus();
2429 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002430 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002431 if (nextFocus != null) {
2432 // do the math to get interesting rect in next focus' coordinates
Kenji Sugimoto827bb442014-04-10 17:46:23 +09002433 Rect focusedRect = mTempRect;
2434 if (currentFocus != null) {
2435 currentFocus.getFocusedRect(focusedRect);
2436 offsetDescendantRectToMyCoords(currentFocus, focusedRect);
2437 offsetRectIntoDescendantCoords(nextFocus, focusedRect);
2438 } else {
2439 focusedRect = null;
2440 }
2441 if (nextFocus.requestFocus(direction, focusedRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002442 return true;
2443 }
2444 }
2445 // we are blocking the key from being handled (by returning true)
2446 // if the global result is going to be some other view within this
2447 // list. this is to acheive the overall goal of having
2448 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002449 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2450 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002451 if (globalNextFocus != null) {
2452 return isViewAncestorOf(globalNextFocus, this);
2453 }
2454 }
2455 }
2456 return false;
2457 }
2458
2459 /**
2460 * Scrolls to the next or previous item if possible.
2461 *
2462 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2463 *
2464 * @return whether selection was moved
2465 */
2466 boolean arrowScroll(int direction) {
2467 try {
2468 mInLayout = true;
2469 final boolean handled = arrowScrollImpl(direction);
2470 if (handled) {
2471 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2472 }
2473 return handled;
2474 } finally {
2475 mInLayout = false;
2476 }
2477 }
2478
2479 /**
Adam Powell2a939112013-03-21 17:08:38 -07002480 * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002481 * to move to. This return a position in the direction given if the selected item
2482 * is fully visible.
Adam Powell2a939112013-03-21 17:08:38 -07002483 *
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002484 * @param selectedView Current selected view to move from
Adam Powell2a939112013-03-21 17:08:38 -07002485 * @param selectedPos Current selected position to move from
2486 * @param direction Direction to move in
2487 * @return Desired selected position after moving in the given direction
2488 */
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002489 private final int nextSelectedPositionForDirection(
2490 View selectedView, int selectedPos, int direction) {
Adam Powell2a939112013-03-21 17:08:38 -07002491 int nextSelected;
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002492
Adam Powell2a939112013-03-21 17:08:38 -07002493 if (direction == View.FOCUS_DOWN) {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002494 final int listBottom = getHeight() - mListPadding.bottom;
2495 if (selectedView != null && selectedView.getBottom() <= listBottom) {
2496 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2497 selectedPos + 1 :
2498 mFirstPosition;
2499 } else {
2500 return INVALID_POSITION;
2501 }
Adam Powell2a939112013-03-21 17:08:38 -07002502 } else {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002503 final int listTop = mListPadding.top;
2504 if (selectedView != null && selectedView.getTop() >= listTop) {
2505 final int lastPos = mFirstPosition + getChildCount() - 1;
2506 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2507 selectedPos - 1 :
2508 lastPos;
2509 } else {
2510 return INVALID_POSITION;
2511 }
Adam Powell2a939112013-03-21 17:08:38 -07002512 }
2513
2514 if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2515 return INVALID_POSITION;
2516 }
2517 return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2518 }
2519
2520 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002521 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2522 * whether there are focusable items etc.
2523 *
2524 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2525 * @return Whether any scrolling, selection or focus change occured.
2526 */
2527 private boolean arrowScrollImpl(int direction) {
2528 if (getChildCount() <= 0) {
2529 return false;
2530 }
2531
2532 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002533 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002534
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002535 int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002536 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2537
2538 // if we are moving focus, we may OVERRIDE the default behavior
2539 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2540 if (focusResult != null) {
2541 nextSelectedPosition = focusResult.getSelectedPosition();
2542 amountToScroll = focusResult.getAmountToScroll();
2543 }
2544
2545 boolean needToRedraw = focusResult != null;
2546 if (nextSelectedPosition != INVALID_POSITION) {
2547 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2548 setSelectedPositionInt(nextSelectedPosition);
2549 setNextSelectedPositionInt(nextSelectedPosition);
2550 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002551 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002552 if (mItemsCanFocus && focusResult == null) {
2553 // there was no new view found to take focus, make sure we
2554 // don't leave focus with the old selection
2555 final View focused = getFocusedChild();
2556 if (focused != null) {
2557 focused.clearFocus();
2558 }
2559 }
2560 needToRedraw = true;
2561 checkSelectionChanged();
2562 }
2563
2564 if (amountToScroll > 0) {
2565 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2566 needToRedraw = true;
2567 }
2568
2569 // if we didn't find a new focusable, make sure any existing focused
2570 // item that was panned off screen gives up focus.
2571 if (mItemsCanFocus && (focusResult == null)
2572 && selectedView != null && selectedView.hasFocus()) {
2573 final View focused = selectedView.findFocus();
Kenji Sugimoto827bb442014-04-10 17:46:23 +09002574 if (focused != null) {
2575 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2576 focused.clearFocus();
2577 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002578 }
2579 }
2580
2581 // if the current selection is panned off, we need to remove the selection
2582 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2583 && !isViewAncestorOf(selectedView, this)) {
2584 selectedView = null;
2585 hideSelector();
2586
2587 // but we don't want to set the ressurect position (that would make subsequent
2588 // unhandled key events bring back the item we just scrolled off!)
2589 mResurrectToPosition = INVALID_POSITION;
2590 }
2591
2592 if (needToRedraw) {
2593 if (selectedView != null) {
Alan Viverettede399392014-05-01 17:20:55 -07002594 positionSelectorLikeFocus(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002595 mSelectedTop = selectedView.getTop();
2596 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002597 if (!awakenScrollBars()) {
2598 invalidate();
2599 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002600 invokeOnItemScrollListener();
2601 return true;
2602 }
2603
2604 return false;
2605 }
2606
2607 /**
2608 * When selection changes, it is possible that the previously selected or the
2609 * next selected item will change its size. If so, we need to offset some folks,
2610 * and re-layout the items as appropriate.
2611 *
2612 * @param selectedView The currently selected view (before changing selection).
2613 * should be <code>null</code> if there was no previous selection.
2614 * @param direction Either {@link android.view.View#FOCUS_UP} or
2615 * {@link android.view.View#FOCUS_DOWN}.
2616 * @param newSelectedPosition The position of the next selection.
2617 * @param newFocusAssigned whether new focus was assigned. This matters because
2618 * when something has focus, we don't want to show selection (ugh).
2619 */
2620 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2621 boolean newFocusAssigned) {
2622 if (newSelectedPosition == INVALID_POSITION) {
2623 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2624 }
2625
2626 // whether or not we are moving down or up, we want to preserve the
2627 // top of whatever view is on top:
2628 // - moving down: the view that had selection
2629 // - moving up: the view that is getting selection
2630 View topView;
2631 View bottomView;
2632 int topViewIndex, bottomViewIndex;
2633 boolean topSelected = false;
2634 final int selectedIndex = mSelectedPosition - mFirstPosition;
2635 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2636 if (direction == View.FOCUS_UP) {
2637 topViewIndex = nextSelectedIndex;
2638 bottomViewIndex = selectedIndex;
2639 topView = getChildAt(topViewIndex);
2640 bottomView = selectedView;
2641 topSelected = true;
2642 } else {
2643 topViewIndex = selectedIndex;
2644 bottomViewIndex = nextSelectedIndex;
2645 topView = selectedView;
2646 bottomView = getChildAt(bottomViewIndex);
2647 }
2648
2649 final int numChildren = getChildCount();
2650
2651 // start with top view: is it changing size?
2652 if (topView != null) {
2653 topView.setSelected(!newFocusAssigned && topSelected);
2654 measureAndAdjustDown(topView, topViewIndex, numChildren);
2655 }
2656
2657 // is the bottom view changing size?
2658 if (bottomView != null) {
2659 bottomView.setSelected(!newFocusAssigned && !topSelected);
2660 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2661 }
2662 }
2663
2664 /**
2665 * Re-measure a child, and if its height changes, lay it out preserving its
2666 * top, and adjust the children below it appropriately.
2667 * @param child The child
2668 * @param childIndex The view group index of the child.
2669 * @param numChildren The number of children in the view group.
2670 */
2671 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2672 int oldHeight = child.getHeight();
2673 measureItem(child);
2674 if (child.getMeasuredHeight() != oldHeight) {
2675 // lay out the view, preserving its top
2676 relayoutMeasuredItem(child);
2677
2678 // adjust views below appropriately
2679 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2680 for (int i = childIndex + 1; i < numChildren; i++) {
2681 getChildAt(i).offsetTopAndBottom(heightDelta);
2682 }
2683 }
2684 }
2685
2686 /**
2687 * Measure a particular list child.
2688 * TODO: unify with setUpChild.
2689 * @param child The child.
2690 */
2691 private void measureItem(View child) {
2692 ViewGroup.LayoutParams p = child.getLayoutParams();
2693 if (p == null) {
2694 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002695 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002696 ViewGroup.LayoutParams.WRAP_CONTENT);
2697 }
2698
2699 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2700 mListPadding.left + mListPadding.right, p.width);
2701 int lpHeight = p.height;
2702 int childHeightSpec;
2703 if (lpHeight > 0) {
2704 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2705 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07002706 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07002707 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002708 }
2709 child.measure(childWidthSpec, childHeightSpec);
2710 }
2711
2712 /**
2713 * Layout a child that has been measured, preserving its top position.
2714 * TODO: unify with setUpChild.
2715 * @param child The child.
2716 */
2717 private void relayoutMeasuredItem(View child) {
2718 final int w = child.getMeasuredWidth();
2719 final int h = child.getMeasuredHeight();
2720 final int childLeft = mListPadding.left;
2721 final int childRight = childLeft + w;
2722 final int childTop = child.getTop();
2723 final int childBottom = childTop + h;
2724 child.layout(childLeft, childTop, childRight, childBottom);
2725 }
2726
2727 /**
2728 * @return The amount to preview next items when arrow srolling.
2729 */
2730 private int getArrowScrollPreviewLength() {
2731 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2732 }
2733
2734 /**
2735 * Determine how much we need to scroll in order to get the next selected view
2736 * visible, with a fading edge showing below as applicable. The amount is
2737 * capped at {@link #getMaxScrollAmount()} .
2738 *
2739 * @param direction either {@link android.view.View#FOCUS_UP} or
2740 * {@link android.view.View#FOCUS_DOWN}.
2741 * @param nextSelectedPosition The position of the next selection, or
2742 * {@link #INVALID_POSITION} if there is no next selectable position
2743 * @return The amount to scroll. Note: this is always positive! Direction
2744 * needs to be taken into account when actually scrolling.
2745 */
2746 private int amountToScroll(int direction, int nextSelectedPosition) {
2747 final int listBottom = getHeight() - mListPadding.bottom;
2748 final int listTop = mListPadding.top;
2749
Justin Ho1ad11b92013-02-25 16:09:20 -08002750 int numChildren = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002751
2752 if (direction == View.FOCUS_DOWN) {
2753 int indexToMakeVisible = numChildren - 1;
2754 if (nextSelectedPosition != INVALID_POSITION) {
2755 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2756 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002757 while (numChildren <= indexToMakeVisible) {
2758 // Child to view is not attached yet.
2759 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2760 numChildren++;
2761 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002762 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2763 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2764
2765 int goalBottom = listBottom;
2766 if (positionToMakeVisible < mItemCount - 1) {
2767 goalBottom -= getArrowScrollPreviewLength();
2768 }
2769
2770 if (viewToMakeVisible.getBottom() <= goalBottom) {
2771 // item is fully visible.
2772 return 0;
2773 }
2774
2775 if (nextSelectedPosition != INVALID_POSITION
2776 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2777 // item already has enough of it visible, changing selection is good enough
2778 return 0;
2779 }
2780
2781 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2782
2783 if ((mFirstPosition + numChildren) == mItemCount) {
2784 // last is last in list -> make sure we don't scroll past it
2785 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2786 amountToScroll = Math.min(amountToScroll, max);
2787 }
2788
2789 return Math.min(amountToScroll, getMaxScrollAmount());
2790 } else {
2791 int indexToMakeVisible = 0;
2792 if (nextSelectedPosition != INVALID_POSITION) {
2793 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2794 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002795 while (indexToMakeVisible < 0) {
2796 // Child to view is not attached yet.
2797 addViewAbove(getChildAt(0), mFirstPosition);
2798 mFirstPosition--;
2799 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002801 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2802 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2803 int goalTop = listTop;
2804 if (positionToMakeVisible > 0) {
2805 goalTop += getArrowScrollPreviewLength();
2806 }
2807 if (viewToMakeVisible.getTop() >= goalTop) {
2808 // item is fully visible.
2809 return 0;
2810 }
2811
2812 if (nextSelectedPosition != INVALID_POSITION &&
2813 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
2814 // item already has enough of it visible, changing selection is good enough
2815 return 0;
2816 }
2817
2818 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
2819 if (mFirstPosition == 0) {
2820 // first is first in list -> make sure we don't scroll past it
2821 final int max = listTop - getChildAt(0).getTop();
2822 amountToScroll = Math.min(amountToScroll, max);
2823 }
2824 return Math.min(amountToScroll, getMaxScrollAmount());
2825 }
2826 }
2827
2828 /**
2829 * Holds results of focus aware arrow scrolling.
2830 */
2831 static private class ArrowScrollFocusResult {
2832 private int mSelectedPosition;
2833 private int mAmountToScroll;
2834
2835 /**
2836 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
2837 */
2838 void populate(int selectedPosition, int amountToScroll) {
2839 mSelectedPosition = selectedPosition;
2840 mAmountToScroll = amountToScroll;
2841 }
2842
2843 public int getSelectedPosition() {
2844 return mSelectedPosition;
2845 }
2846
2847 public int getAmountToScroll() {
2848 return mAmountToScroll;
2849 }
2850 }
2851
2852 /**
2853 * @param direction either {@link android.view.View#FOCUS_UP} or
2854 * {@link android.view.View#FOCUS_DOWN}.
2855 * @return The position of the next selectable position of the views that
2856 * are currently visible, taking into account the fact that there might
2857 * be no selection. Returns {@link #INVALID_POSITION} if there is no
2858 * selectable view on screen in the given direction.
2859 */
2860 private int lookForSelectablePositionOnScreen(int direction) {
2861 final int firstPosition = mFirstPosition;
2862 if (direction == View.FOCUS_DOWN) {
2863 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2864 mSelectedPosition + 1 :
2865 firstPosition;
2866 if (startPos >= mAdapter.getCount()) {
2867 return INVALID_POSITION;
2868 }
2869 if (startPos < firstPosition) {
2870 startPos = firstPosition;
2871 }
2872
2873 final int lastVisiblePos = getLastVisiblePosition();
2874 final ListAdapter adapter = getAdapter();
2875 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
2876 if (adapter.isEnabled(pos)
2877 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2878 return pos;
2879 }
2880 }
2881 } else {
2882 int last = firstPosition + getChildCount() - 1;
2883 int startPos = (mSelectedPosition != INVALID_POSITION) ?
2884 mSelectedPosition - 1 :
2885 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08002886 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002887 return INVALID_POSITION;
2888 }
2889 if (startPos > last) {
2890 startPos = last;
2891 }
2892
2893 final ListAdapter adapter = getAdapter();
2894 for (int pos = startPos; pos >= firstPosition; pos--) {
2895 if (adapter.isEnabled(pos)
2896 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
2897 return pos;
2898 }
2899 }
2900 }
2901 return INVALID_POSITION;
2902 }
2903
2904 /**
2905 * Do an arrow scroll based on focus searching. If a new view is
2906 * given focus, return the selection delta and amount to scroll via
2907 * an {@link ArrowScrollFocusResult}, otherwise, return null.
2908 *
2909 * @param direction either {@link android.view.View#FOCUS_UP} or
2910 * {@link android.view.View#FOCUS_DOWN}.
2911 * @return The result if focus has changed, or <code>null</code>.
2912 */
2913 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
2914 final View selectedView = getSelectedView();
2915 View newFocus;
2916 if (selectedView != null && selectedView.hasFocus()) {
2917 View oldFocus = selectedView.findFocus();
2918 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
2919 } else {
2920 if (direction == View.FOCUS_DOWN) {
2921 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
2922 final int listTop = mListPadding.top +
2923 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2924 final int ySearchPoint =
2925 (selectedView != null && selectedView.getTop() > listTop) ?
2926 selectedView.getTop() :
2927 listTop;
2928 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2929 } else {
2930 final boolean bottomFadingEdgeShowing =
2931 (mFirstPosition + getChildCount() - 1) < mItemCount;
2932 final int listBottom = getHeight() - mListPadding.bottom -
2933 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
2934 final int ySearchPoint =
2935 (selectedView != null && selectedView.getBottom() < listBottom) ?
2936 selectedView.getBottom() :
2937 listBottom;
2938 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
2939 }
2940 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
2941 }
2942
2943 if (newFocus != null) {
2944 final int positionOfNewFocus = positionOfNewFocus(newFocus);
2945
2946 // if the focus change is in a different new position, make sure
2947 // we aren't jumping over another selectable position
2948 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
2949 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
2950 if (selectablePosition != INVALID_POSITION &&
2951 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
2952 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
2953 return null;
2954 }
2955 }
2956
2957 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
2958
2959 final int maxScrollAmount = getMaxScrollAmount();
2960 if (focusScroll < maxScrollAmount) {
2961 // not moving too far, safe to give next view focus
2962 newFocus.requestFocus(direction);
2963 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
2964 return mArrowScrollFocusResult;
2965 } else if (distanceToView(newFocus) < maxScrollAmount){
2966 // Case to consider:
2967 // too far to get entire next focusable on screen, but by going
2968 // max scroll amount, we are getting it at least partially in view,
2969 // so give it focus and scroll the max ammount.
2970 newFocus.requestFocus(direction);
2971 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
2972 return mArrowScrollFocusResult;
2973 }
2974 }
2975 return null;
2976 }
2977
2978 /**
2979 * @param newFocus The view that would have focus.
2980 * @return the position that contains newFocus
2981 */
2982 private int positionOfNewFocus(View newFocus) {
2983 final int numChildren = getChildCount();
2984 for (int i = 0; i < numChildren; i++) {
2985 final View child = getChildAt(i);
2986 if (isViewAncestorOf(newFocus, child)) {
2987 return mFirstPosition + i;
2988 }
2989 }
2990 throw new IllegalArgumentException("newFocus is not a child of any of the"
2991 + " children of the list!");
2992 }
2993
2994 /**
2995 * Return true if child is an ancestor of parent, (or equal to the parent).
2996 */
2997 private boolean isViewAncestorOf(View child, View parent) {
2998 if (child == parent) {
2999 return true;
3000 }
3001
3002 final ViewParent theParent = child.getParent();
3003 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
3004 }
3005
3006 /**
3007 * Determine how much we need to scroll in order to get newFocus in view.
3008 * @param direction either {@link android.view.View#FOCUS_UP} or
3009 * {@link android.view.View#FOCUS_DOWN}.
3010 * @param newFocus The view that would take focus.
3011 * @param positionOfNewFocus The position of the list item containing newFocus
3012 * @return The amount to scroll. Note: this is always positive! Direction
3013 * needs to be taken into account when actually scrolling.
3014 */
3015 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
3016 int amountToScroll = 0;
3017 newFocus.getDrawingRect(mTempRect);
3018 offsetDescendantRectToMyCoords(newFocus, mTempRect);
3019 if (direction == View.FOCUS_UP) {
3020 if (mTempRect.top < mListPadding.top) {
3021 amountToScroll = mListPadding.top - mTempRect.top;
3022 if (positionOfNewFocus > 0) {
3023 amountToScroll += getArrowScrollPreviewLength();
3024 }
3025 }
3026 } else {
3027 final int listBottom = getHeight() - mListPadding.bottom;
3028 if (mTempRect.bottom > listBottom) {
3029 amountToScroll = mTempRect.bottom - listBottom;
3030 if (positionOfNewFocus < mItemCount - 1) {
3031 amountToScroll += getArrowScrollPreviewLength();
3032 }
3033 }
3034 }
3035 return amountToScroll;
3036 }
3037
3038 /**
3039 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003040 * direction.
3041 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003042 * @param descendant A descendant of this list.
3043 * @return The distance, or 0 if the nearest edge is already on screen.
3044 */
3045 private int distanceToView(View descendant) {
3046 int distance = 0;
3047 descendant.getDrawingRect(mTempRect);
3048 offsetDescendantRectToMyCoords(descendant, mTempRect);
3049 final int listBottom = mBottom - mTop - mListPadding.bottom;
3050 if (mTempRect.bottom < mListPadding.top) {
3051 distance = mListPadding.top - mTempRect.bottom;
3052 } else if (mTempRect.top > listBottom) {
3053 distance = mTempRect.top - listBottom;
3054 }
3055 return distance;
3056 }
3057
3058
3059 /**
3060 * Scroll the children by amount, adding a view at the end and removing
3061 * views that fall off as necessary.
3062 *
3063 * @param amount The amount (positive or negative) to scroll.
3064 */
3065 private void scrollListItemsBy(int amount) {
3066 offsetChildrenTopAndBottom(amount);
3067
3068 final int listBottom = getHeight() - mListPadding.bottom;
3069 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003070 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003071
3072 if (amount < 0) {
3073 // shifted items up
3074
3075 // may need to pan views into the bottom space
3076 int numChildren = getChildCount();
3077 View last = getChildAt(numChildren - 1);
3078 while (last.getBottom() < listBottom) {
3079 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3080 if (lastVisiblePosition < mItemCount - 1) {
3081 last = addViewBelow(last, lastVisiblePosition);
3082 numChildren++;
3083 } else {
3084 break;
3085 }
3086 }
3087
3088 // may have brought in the last child of the list that is skinnier
3089 // than the fading edge, thereby leaving space at the end. need
3090 // to shift back
3091 if (last.getBottom() < listBottom) {
3092 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3093 }
3094
3095 // top views may be panned off screen
3096 View first = getChildAt(0);
3097 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003098 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3099 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003100 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003101 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003102 detachViewFromParent(first);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003103 first = getChildAt(0);
3104 mFirstPosition++;
3105 }
3106 } else {
3107 // shifted items down
3108 View first = getChildAt(0);
3109
3110 // may need to pan views into top
3111 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3112 first = addViewAbove(first, mFirstPosition);
3113 mFirstPosition--;
3114 }
3115
3116 // may have brought the very first child of the list in too far and
3117 // need to shift it back
3118 if (first.getTop() > listTop) {
3119 offsetChildrenTopAndBottom(listTop - first.getTop());
3120 }
3121
3122 int lastIndex = getChildCount() - 1;
3123 View last = getChildAt(lastIndex);
3124
3125 // bottom view may be panned off screen
3126 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003127 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3128 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003129 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003130 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003131 detachViewFromParent(last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003132 last = getChildAt(--lastIndex);
3133 }
3134 }
3135 }
3136
3137 private View addViewAbove(View theView, int position) {
3138 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08003139 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003140 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003141 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3142 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003143 return view;
3144 }
3145
3146 private View addViewBelow(View theView, int position) {
3147 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08003148 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003149 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003150 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3151 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003152 return view;
3153 }
3154
3155 /**
3156 * Indicates that the views created by the ListAdapter can contain focusable
3157 * items.
3158 *
3159 * @param itemsCanFocus true if items can get focus, false otherwise
3160 */
3161 public void setItemsCanFocus(boolean itemsCanFocus) {
3162 mItemsCanFocus = itemsCanFocus;
3163 if (!itemsCanFocus) {
3164 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3165 }
3166 }
3167
3168 /**
3169 * @return Whether the views created by the ListAdapter can contain focusable
3170 * items.
3171 */
3172 public boolean getItemsCanFocus() {
3173 return mItemsCanFocus;
3174 }
3175
3176 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003177 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003178 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f52009-05-15 16:03:59 -07003179 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003180 if (retValue) {
3181 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003182 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003183 View first = getChildAt(0);
3184 if (first == null || first.getTop() > listTop) {
3185 return false;
3186 }
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003187 final int listBottom = getHeight() -
3188 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003189 View last = getChildAt(getChildCount() - 1);
3190 if (last == null || last.getBottom() < listBottom) {
3191 return false;
3192 }
3193 }
3194 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003195 }
3196
3197 @Override
3198 public void setCacheColorHint(int color) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003199 final boolean opaque = (color >>> 24) == 0xFF;
3200 mIsCacheColorOpaque = opaque;
3201 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003202 if (mDividerPaint == null) {
3203 mDividerPaint = new Paint();
3204 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003205 mDividerPaint.setColor(color);
3206 }
Romain Guy24443ea2009-05-11 11:56:30 -07003207 super.setCacheColorHint(color);
3208 }
Adam Powell637d3372010-08-25 14:37:03 -07003209
3210 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3211 final int height = drawable.getMinimumHeight();
3212
3213 canvas.save();
3214 canvas.clipRect(bounds);
3215
3216 final int span = bounds.bottom - bounds.top;
3217 if (span < height) {
3218 bounds.top = bounds.bottom - height;
3219 }
3220
3221 drawable.setBounds(bounds);
3222 drawable.draw(canvas);
3223
3224 canvas.restore();
3225 }
3226
3227 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3228 final int height = drawable.getMinimumHeight();
3229
3230 canvas.save();
3231 canvas.clipRect(bounds);
3232
3233 final int span = bounds.bottom - bounds.top;
3234 if (span < height) {
3235 bounds.bottom = bounds.top + height;
3236 }
3237
3238 drawable.setBounds(bounds);
3239 drawable.draw(canvas);
3240
3241 canvas.restore();
3242 }
3243
Romain Guy24443ea2009-05-11 11:56:30 -07003244 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003245 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003246 if (mCachingStarted) {
3247 mCachingActive = true;
3248 }
3249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003250 // Draw the dividers
3251 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003252 final Drawable overscrollHeader = mOverScrollHeader;
3253 final Drawable overscrollFooter = mOverScrollFooter;
3254 final boolean drawOverscrollHeader = overscrollHeader != null;
3255 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003256 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003257
Adam Powell637d3372010-08-25 14:37:03 -07003258 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003259 // Only modify the top and bottom in the loop, we set the left and right here
3260 final Rect bounds = mTempRect;
3261 bounds.left = mPaddingLeft;
3262 bounds.right = mRight - mLeft - mPaddingRight;
3263
3264 final int count = getChildCount();
3265 final int headerCount = mHeaderViewInfos.size();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003266 final int itemCount = mItemCount;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003267 final int footerLimit = (itemCount - mFooterViewInfos.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003268 final boolean headerDividers = mHeaderDividersEnabled;
3269 final boolean footerDividers = mFooterDividersEnabled;
3270 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003271 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3272 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003273 // If the list is opaque *and* the background is not, we want to
3274 // fill a rect where the dividers would be for non-selectable items
3275 // If the list is opaque and the background is also opaque, we don't
3276 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003277 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003278
3279 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003280 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003281 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003282 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003283 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003284
Adam Powell94566552011-01-05 23:25:33 -08003285 int effectivePaddingTop = 0;
3286 int effectivePaddingBottom = 0;
3287 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3288 effectivePaddingTop = mListPadding.top;
3289 effectivePaddingBottom = mListPadding.bottom;
3290 }
3291
3292 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003293 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003294 int bottom = 0;
Adam Powell0b8bb422010-02-08 14:30:45 -08003295
Adam Powell637d3372010-08-25 14:37:03 -07003296 // Draw top divider or header for overscroll
3297 final int scrollY = mScrollY;
3298 if (count > 0 && scrollY < 0) {
3299 if (drawOverscrollHeader) {
3300 bounds.bottom = 0;
3301 bounds.top = scrollY;
3302 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3303 } else if (drawDividers) {
3304 bounds.bottom = 0;
3305 bounds.top = -dividerHeight;
3306 drawDivider(canvas, bounds, -1);
3307 }
3308 }
3309
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003310 for (int i = 0; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003311 final int itemIndex = (first + i);
3312 final boolean isHeader = (itemIndex < headerCount);
3313 final boolean isFooter = (itemIndex >= footerLimit);
3314 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3315 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003316 bottom = child.getBottom();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003317 final boolean isLastItem = (i == (count - 1));
Adam Powell637d3372010-08-25 14:37:03 -07003318
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003319 if (drawDividers && (bottom < listBottom)
3320 && !(drawOverscrollFooter && isLastItem)) {
3321 final int nextIndex = (itemIndex + 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003322 // Draw dividers between enabled items, headers
3323 // and/or footers when enabled and requested, and
3324 // after the last enabled item.
3325 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3326 && (nextIndex >= headerCount)) && (isLastItem
3327 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3328 && (nextIndex < footerLimit)))) {
Adam Powell637d3372010-08-25 14:37:03 -07003329 bounds.top = bottom;
3330 bounds.bottom = bottom + dividerHeight;
3331 drawDivider(canvas, bounds, i);
3332 } else if (fillForMissingDividers) {
3333 bounds.top = bottom;
3334 bounds.bottom = bottom + dividerHeight;
3335 canvas.drawRect(bounds, paint);
3336 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003337 }
3338 }
3339 }
Adam Powell637d3372010-08-25 14:37:03 -07003340
3341 final int overFooterBottom = mBottom + mScrollY;
3342 if (drawOverscrollFooter && first + count == itemCount &&
3343 overFooterBottom > bottom) {
3344 bounds.top = bottom;
3345 bounds.bottom = overFooterBottom;
3346 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3347 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003348 } else {
3349 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003350
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003351 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003352
Adam Powell637d3372010-08-25 14:37:03 -07003353 if (count > 0 && drawOverscrollHeader) {
3354 bounds.top = scrollY;
3355 bounds.bottom = getChildAt(0).getTop();
3356 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3357 }
3358
3359 final int start = drawOverscrollHeader ? 1 : 0;
3360 for (int i = start; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003361 final int itemIndex = (first + i);
3362 final boolean isHeader = (itemIndex < headerCount);
3363 final boolean isFooter = (itemIndex >= footerLimit);
3364 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3365 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003366 top = child.getTop();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003367 if (drawDividers && (top > effectivePaddingTop)) {
3368 final boolean isFirstItem = (i == start);
3369 final int previousIndex = (itemIndex - 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003370 // Draw dividers between enabled items, headers
3371 // and/or footers when enabled and requested, and
3372 // before the first enabled item.
3373 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3374 && (previousIndex >= headerCount)) && (isFirstItem ||
3375 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3376 && (previousIndex < footerLimit)))) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003377 bounds.top = top - dividerHeight;
3378 bounds.bottom = top;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003379 // Give the method the child ABOVE the divider,
3380 // so we subtract one from our child position.
3381 // Give -1 when there is no child above the
Romain Guy8f1344f52009-05-15 16:03:59 -07003382 // divider.
3383 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003384 } else if (fillForMissingDividers) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003385 bounds.top = top - dividerHeight;
3386 bounds.bottom = top;
3387 canvas.drawRect(bounds, paint);
3388 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003389 }
3390 }
3391 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003392
Romain Guy179de8a2010-07-09 13:27:00 -07003393 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003394 if (drawOverscrollFooter) {
3395 final int absListBottom = mBottom;
3396 bounds.top = absListBottom;
3397 bounds.bottom = absListBottom + scrollY;
3398 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3399 } else if (drawDividers) {
3400 bounds.top = listBottom;
3401 bounds.bottom = listBottom + dividerHeight;
3402 drawDivider(canvas, bounds, -1);
3403 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003404 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003405 }
3406 }
3407
3408 // Draw the indicators (these should be drawn above the dividers) and children
3409 super.dispatchDraw(canvas);
3410 }
3411
Romain Guy0211a0a2011-02-14 16:34:59 -08003412 @Override
3413 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3414 boolean more = super.drawChild(canvas, child, drawingTime);
3415 if (mCachingActive && child.mCachingFailed) {
3416 mCachingActive = false;
3417 }
3418 return more;
3419 }
3420
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003421 /**
3422 * Draws a divider for the given child in the given bounds.
3423 *
3424 * @param canvas The canvas to draw to.
3425 * @param bounds The bounds of the divider.
3426 * @param childIndex The index of child (of the View) above the divider.
3427 * This will be -1 if there is no child above the divider to be
3428 * drawn.
3429 */
3430 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3431 // This widget draws the same divider for all children
3432 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003433
Romain Guy95930e12010-10-04 13:46:02 -07003434 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003435 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003436 }
3437
3438 /**
3439 * Returns the drawable that will be drawn between each item in the list.
3440 *
3441 * @return the current drawable drawn between list elements
Alan Viverette7b2f8642015-06-01 10:30:21 -07003442 * @attr ref R.styleable#ListView_divider
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003443 */
Alan Viverette7b2f8642015-06-01 10:30:21 -07003444 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003445 public Drawable getDivider() {
3446 return mDivider;
3447 }
3448
3449 /**
Alan Viverette7b2f8642015-06-01 10:30:21 -07003450 * Sets the drawable that will be drawn between each item in the list.
3451 * <p>
3452 * <strong>Note:</strong> If the drawable does not have an intrinsic
3453 * height, you should also call {@link #setDividerHeight(int)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003454 *
Alan Viverette7b2f8642015-06-01 10:30:21 -07003455 * @param divider the drawable to use
3456 * @attr ref R.styleable#ListView_divider
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003457 */
Alan Viverette7b2f8642015-06-01 10:30:21 -07003458 public void setDivider(@Nullable Drawable divider) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003459 if (divider != null) {
3460 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003461 } else {
3462 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003463 }
3464 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003465 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003466 requestLayout();
3467 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003468 }
3469
3470 /**
3471 * @return Returns the height of the divider that will be drawn between each item in the list.
3472 */
3473 public int getDividerHeight() {
3474 return mDividerHeight;
3475 }
3476
3477 /**
3478 * Sets the height of the divider that will be drawn between each item in the list. Calling
3479 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3480 *
3481 * @param height The new height of the divider in pixels.
3482 */
3483 public void setDividerHeight(int height) {
3484 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003485 requestLayout();
3486 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003487 }
3488
3489 /**
3490 * Enables or disables the drawing of the divider for header views.
3491 *
3492 * @param headerDividersEnabled True to draw the headers, false otherwise.
3493 *
3494 * @see #setFooterDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003495 * @see #areHeaderDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003496 * @see #addHeaderView(android.view.View)
3497 */
3498 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3499 mHeaderDividersEnabled = headerDividersEnabled;
3500 invalidate();
3501 }
3502
3503 /**
Alan Viverette55421032013-06-11 17:50:56 -07003504 * @return Whether the drawing of the divider for header views is enabled
3505 *
3506 * @see #setHeaderDividersEnabled(boolean)
3507 */
3508 public boolean areHeaderDividersEnabled() {
3509 return mHeaderDividersEnabled;
3510 }
3511
3512 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003513 * Enables or disables the drawing of the divider for footer views.
3514 *
3515 * @param footerDividersEnabled True to draw the footers, false otherwise.
3516 *
3517 * @see #setHeaderDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003518 * @see #areFooterDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003519 * @see #addFooterView(android.view.View)
3520 */
3521 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3522 mFooterDividersEnabled = footerDividersEnabled;
3523 invalidate();
3524 }
Alan Viverette55421032013-06-11 17:50:56 -07003525
3526 /**
3527 * @return Whether the drawing of the divider for footer views is enabled
3528 *
3529 * @see #setFooterDividersEnabled(boolean)
3530 */
3531 public boolean areFooterDividersEnabled() {
3532 return mFooterDividersEnabled;
3533 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003534
Adam Powell637d3372010-08-25 14:37:03 -07003535 /**
3536 * Sets the drawable that will be drawn above all other list content.
3537 * This area can become visible when the user overscrolls the list.
3538 *
3539 * @param header The drawable to use
3540 */
3541 public void setOverscrollHeader(Drawable header) {
3542 mOverScrollHeader = header;
3543 if (mScrollY < 0) {
3544 invalidate();
3545 }
3546 }
3547
3548 /**
3549 * @return The drawable that will be drawn above all other list content
3550 */
3551 public Drawable getOverscrollHeader() {
3552 return mOverScrollHeader;
3553 }
3554
3555 /**
3556 * Sets the drawable that will be drawn below all other list content.
3557 * This area can become visible when the user overscrolls the list,
3558 * or when the list's content does not fully fill the container area.
3559 *
3560 * @param footer The drawable to use
3561 */
3562 public void setOverscrollFooter(Drawable footer) {
3563 mOverScrollFooter = footer;
3564 invalidate();
3565 }
3566
3567 /**
3568 * @return The drawable that will be drawn below all other list content
3569 */
3570 public Drawable getOverscrollFooter() {
3571 return mOverScrollFooter;
3572 }
3573
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003574 @Override
3575 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3576 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3577
Adam Powelle1bf4862011-09-02 16:56:20 -07003578 final ListAdapter adapter = mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003579 int closetChildIndex = -1;
Adam Powelldcce1212011-10-31 16:41:21 -07003580 int closestChildTop = 0;
Adam Powelle1bf4862011-09-02 16:56:20 -07003581 if (adapter != null && gainFocus && previouslyFocusedRect != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003582 previouslyFocusedRect.offset(mScrollX, mScrollY);
3583
Adam Powelld7507832010-02-18 15:40:33 -08003584 // Don't cache the result of getChildCount or mFirstPosition here,
3585 // it could change in layoutChildren.
3586 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003587 mLayoutMode = LAYOUT_NORMAL;
3588 layoutChildren();
3589 }
3590
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003591 // figure out which item should be selected based on previously
3592 // focused rect
3593 Rect otherRect = mTempRect;
3594 int minDistance = Integer.MAX_VALUE;
3595 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003596 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003597
3598 for (int i = 0; i < childCount; i++) {
3599 // only consider selectable views
3600 if (!adapter.isEnabled(firstPosition + i)) {
3601 continue;
3602 }
3603
3604 View other = getChildAt(i);
3605 other.getDrawingRect(otherRect);
3606 offsetDescendantRectToMyCoords(other, otherRect);
3607 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3608
3609 if (distance < minDistance) {
3610 minDistance = distance;
3611 closetChildIndex = i;
Adam Powelldcce1212011-10-31 16:41:21 -07003612 closestChildTop = other.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003613 }
3614 }
3615 }
3616
3617 if (closetChildIndex >= 0) {
Adam Powelldcce1212011-10-31 16:41:21 -07003618 setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003619 } else {
3620 requestLayout();
3621 }
3622 }
3623
3624
3625 /*
3626 * (non-Javadoc)
3627 *
3628 * Children specified in XML are assumed to be header views. After we have
3629 * parsed them move them out of the children list and into mHeaderViews.
3630 */
3631 @Override
3632 protected void onFinishInflate() {
3633 super.onFinishInflate();
3634
3635 int count = getChildCount();
3636 if (count > 0) {
3637 for (int i = 0; i < count; ++i) {
3638 addHeaderView(getChildAt(i));
3639 }
3640 removeAllViews();
3641 }
3642 }
3643
3644 /* (non-Javadoc)
3645 * @see android.view.View#findViewById(int)
3646 * First look in our children, then in any header and footer views that may be scrolled off.
3647 */
3648 @Override
Tor Norbye7b9c9122013-05-30 16:48:33 -07003649 protected View findViewTraversal(@IdRes int id) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003650 View v;
3651 v = super.findViewTraversal(id);
3652 if (v == null) {
3653 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3654 if (v != null) {
3655 return v;
3656 }
3657 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3658 if (v != null) {
3659 return v;
3660 }
3661 }
3662 return v;
3663 }
3664
3665 /* (non-Javadoc)
3666 *
3667 * Look in the passed in list of headers or footers for the view.
3668 */
3669 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
3670 if (where != null) {
3671 int len = where.size();
3672 View v;
3673
3674 for (int i = 0; i < len; i++) {
3675 v = where.get(i).view;
3676
3677 if (!v.isRootNamespace()) {
3678 v = v.findViewById(id);
3679
3680 if (v != null) {
3681 return v;
3682 }
3683 }
3684 }
3685 }
3686 return null;
3687 }
3688
3689 /* (non-Javadoc)
Jeff Brown4e6319b2010-12-13 10:36:51 -08003690 * @see android.view.View#findViewWithTag(Object)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003691 * First look in our children, then in any header and footer views that may be scrolled off.
3692 */
3693 @Override
3694 protected View findViewWithTagTraversal(Object tag) {
3695 View v;
3696 v = super.findViewWithTagTraversal(tag);
3697 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003698 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003699 if (v != null) {
3700 return v;
3701 }
3702
Jeff Brown4e6319b2010-12-13 10:36:51 -08003703 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003704 if (v != null) {
3705 return v;
3706 }
3707 }
3708 return v;
3709 }
3710
3711 /* (non-Javadoc)
3712 *
3713 * Look in the passed in list of headers or footers for the view with the tag.
3714 */
Jeff Brown4e6319b2010-12-13 10:36:51 -08003715 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003716 if (where != null) {
3717 int len = where.size();
3718 View v;
3719
3720 for (int i = 0; i < len; i++) {
3721 v = where.get(i).view;
3722
3723 if (!v.isRootNamespace()) {
3724 v = v.findViewWithTag(tag);
3725
3726 if (v != null) {
3727 return v;
3728 }
3729 }
3730 }
3731 }
3732 return null;
3733 }
3734
Jeff Brown4e6319b2010-12-13 10:36:51 -08003735 /**
3736 * @hide
3737 * @see android.view.View#findViewByPredicate(Predicate)
3738 * First look in our children, then in any header and footer views that may be scrolled off.
3739 */
3740 @Override
Jeff Brown4dfbec22011-08-15 14:55:37 -07003741 protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003742 View v;
Jeff Brown4dfbec22011-08-15 14:55:37 -07003743 v = super.findViewByPredicateTraversal(predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003744 if (v == null) {
Jeff Brown4dfbec22011-08-15 14:55:37 -07003745 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003746 if (v != null) {
3747 return v;
3748 }
3749
Jeff Brown4dfbec22011-08-15 14:55:37 -07003750 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003751 if (v != null) {
3752 return v;
3753 }
3754 }
3755 return v;
3756 }
3757
3758 /* (non-Javadoc)
3759 *
3760 * Look in the passed in list of headers or footers for the first view that matches
3761 * the predicate.
3762 */
3763 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
Jeff Brown4dfbec22011-08-15 14:55:37 -07003764 Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003765 if (where != null) {
3766 int len = where.size();
3767 View v;
3768
3769 for (int i = 0; i < len; i++) {
3770 v = where.get(i).view;
3771
Jeff Brown4dfbec22011-08-15 14:55:37 -07003772 if (v != childToSkip && !v.isRootNamespace()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003773 v = v.findViewByPredicate(predicate);
3774
3775 if (v != null) {
3776 return v;
3777 }
3778 }
3779 }
3780 }
3781 return null;
3782 }
3783
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003784 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003785 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003786 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
3787 *
3788 * @return A new array which contains the id of each checked item in the
3789 * list.
3790 *
Adam Powell463ceff2010-03-09 11:50:51 -08003791 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003792 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003793 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003794 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003795 // Use new behavior that correctly handles stable ID mapping.
3796 if (mAdapter != null && mAdapter.hasStableIds()) {
3797 return getCheckedItemIds();
3798 }
3799
3800 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3801 // Fall back to it to support legacy apps.
3802 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3803 final SparseBooleanArray states = mCheckStates;
3804 final int count = states.size();
3805 final long[] ids = new long[count];
3806 final ListAdapter adapter = mAdapter;
3807
3808 int checkedCount = 0;
3809 for (int i = 0; i < count; i++) {
3810 if (states.valueAt(i)) {
3811 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
3812 }
3813 }
3814
3815 // Trim array if needed. mCheckStates may contain false values
3816 // resulting in checkedCount being smaller than count.
3817 if (checkedCount == count) {
3818 return ids;
3819 } else {
3820 final long[] result = new long[checkedCount];
3821 System.arraycopy(ids, 0, result, 0, checkedCount);
3822
3823 return result;
3824 }
3825 }
3826 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08003827 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003828
3829 @Override
Alan Viverette441b4372014-02-12 13:30:20 -08003830 int getHeightForPosition(int position) {
3831 final int height = super.getHeightForPosition(position);
3832 if (shouldAdjustHeightForDivider(position)) {
3833 return height + mDividerHeight;
3834 }
3835 return height;
3836 }
3837
3838 private boolean shouldAdjustHeightForDivider(int itemIndex) {
3839 final int dividerHeight = mDividerHeight;
3840 final Drawable overscrollHeader = mOverScrollHeader;
3841 final Drawable overscrollFooter = mOverScrollFooter;
3842 final boolean drawOverscrollHeader = overscrollHeader != null;
3843 final boolean drawOverscrollFooter = overscrollFooter != null;
3844 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
3845
3846 if (drawDividers) {
3847 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
3848 final int itemCount = mItemCount;
3849 final int headerCount = mHeaderViewInfos.size();
3850 final int footerLimit = (itemCount - mFooterViewInfos.size());
3851 final boolean isHeader = (itemIndex < headerCount);
3852 final boolean isFooter = (itemIndex >= footerLimit);
3853 final boolean headerDividers = mHeaderDividersEnabled;
3854 final boolean footerDividers = mFooterDividersEnabled;
3855 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3856 final ListAdapter adapter = mAdapter;
3857 if (!mStackFromBottom) {
3858 final boolean isLastItem = (itemIndex == (itemCount - 1));
3859 if (!drawOverscrollFooter || !isLastItem) {
3860 final int nextIndex = itemIndex + 1;
3861 // Draw dividers between enabled items, headers
3862 // and/or footers when enabled and requested, and
3863 // after the last enabled item.
3864 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3865 && (nextIndex >= headerCount)) && (isLastItem
3866 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3867 && (nextIndex < footerLimit)))) {
3868 return true;
3869 } else if (fillForMissingDividers) {
3870 return true;
3871 }
3872 }
3873 } else {
3874 final int start = drawOverscrollHeader ? 1 : 0;
3875 final boolean isFirstItem = (itemIndex == start);
3876 if (!isFirstItem) {
3877 final int previousIndex = (itemIndex - 1);
3878 // Draw dividers between enabled items, headers
3879 // and/or footers when enabled and requested, and
3880 // before the first enabled item.
3881 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3882 && (previousIndex >= headerCount)) && (isFirstItem ||
3883 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3884 && (previousIndex < footerLimit)))) {
3885 return true;
3886 } else if (fillForMissingDividers) {
3887 return true;
3888 }
3889 }
3890 }
3891 }
3892 }
3893
3894 return false;
3895 }
3896
3897 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08003898 public CharSequence getAccessibilityClassName() {
3899 return ListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003900 }
3901
Alan Viverettea54956a2015-01-07 16:05:02 -08003902 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003903 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08003904 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3905 super.onInitializeAccessibilityNodeInfoInternal(info);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003906
Alan Viverette77c180a2014-09-08 15:30:34 -07003907 final int rowsCount = getCount();
Alan Viverette76769ae2014-02-12 16:38:10 -08003908 final int selectionMode = getSelectionModeForAccessibility();
Alan Viverette77c180a2014-09-08 15:30:34 -07003909 final CollectionInfo collectionInfo = CollectionInfo.obtain(
3910 rowsCount, 1, false, selectionMode);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003911 info.setCollectionInfo(collectionInfo);
Alan Viverette23f44322015-04-06 16:04:56 -07003912
3913 if (rowsCount > 0) {
3914 info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
3915 }
3916 }
3917
3918 /** @hide */
3919 @Override
3920 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
3921 if (super.performAccessibilityActionInternal(action, arguments)) {
3922 return true;
3923 }
3924
3925 switch (action) {
3926 case R.id.accessibilityActionScrollToPosition: {
3927 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
3928 final int position = Math.min(row, getCount() - 1);
3929 if (row >= 0) {
3930 // The accessibility service gets data asynchronously, so
3931 // we'll be a little lenient by clamping the last position.
3932 smoothScrollToPosition(position);
3933 return true;
3934 }
3935 } break;
3936 }
3937
3938 return false;
Alan Viverette5b2081d2013-08-28 10:43:07 -07003939 }
3940
3941 @Override
3942 public void onInitializeAccessibilityNodeInfoForItem(
3943 View view, int position, AccessibilityNodeInfo info) {
3944 super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
3945
3946 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
3947 final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
Alan Viverette76769ae2014-02-12 16:38:10 -08003948 final boolean isSelected = isItemChecked(position);
3949 final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
Alan Viverette77c180a2014-09-08 15:30:34 -07003950 position, 1, 0, 1, isHeading, isSelected);
Alan Viverette5b2081d2013-08-28 10:43:07 -07003951 info.setCollectionItemInfo(itemInfo);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08003952 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07003953
3954 /** @hide */
3955 @Override
3956 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
3957 super.encodeProperties(encoder);
3958
3959 encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
3960 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003961}