blob: 9b497866486d4f6f0007f052b74926a1facc964e [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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.IdRes;
Siva Velusamy94a6d152015-05-05 15:07:00 -070020import android.annotation.NonNull;
Yigit Boyar03633412016-03-24 17:44:28 -070021import android.annotation.Nullable;
Mathew Inwood978c6e22018-08-21 15:58:55 +010022import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.Context;
Winson Chung499cb9f2010-07-16 11:18:17 -070024import android.content.Intent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.TypedArray;
26import android.graphics.Canvas;
Romain Guy8f1344f52009-05-15 16:03:59 -070027import android.graphics.Paint;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080028import android.graphics.PixelFormat;
29import android.graphics.Rect;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080030import android.graphics.drawable.Drawable;
Mathew Inwood31755f92018-12-20 13:53:36 +000031import android.os.Build;
Yigit Boyar03633412016-03-24 17:44:28 -070032import android.os.Bundle;
33import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.AttributeSet;
Daichi Hironoa3e6a952017-05-09 18:17:34 +090035import android.util.Log;
alanv30ee76c2012-09-07 10:31:16 -070036import android.util.MathUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.util.SparseBooleanArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038import android.view.FocusFinder;
39import android.view.KeyEvent;
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -080040import android.view.SoundEffectConstants;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.view.View;
42import android.view.ViewDebug;
43import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070044import android.view.ViewHierarchyEncoder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.view.ViewParent;
Alan Viverette3e141622014-02-18 17:05:13 -080046import android.view.ViewRootImpl;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080047import android.view.accessibility.AccessibilityNodeInfo;
Alan Viverette23f44322015-04-06 16:04:56 -070048import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Alan Viverette5b2081d2013-08-28 10:43:07 -070049import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
50import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
Alan Viverette3e141622014-02-18 17:05:13 -080051import android.view.accessibility.AccessibilityNodeProvider;
Winson Chung499cb9f2010-07-16 11:18:17 -070052import android.widget.RemoteViews.RemoteView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Aurimas Liutikas99441c52016-10-11 16:48:32 -070054import com.android.internal.R;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070055
56import com.google.android.collect.Lists;
57
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058import java.util.ArrayList;
Yigit Boyarb742b872016-05-06 16:11:12 -070059import java.util.List;
Paul Duffinca4964c2017-02-07 15:04:10 +000060import java.util.function.Predicate;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
62/*
63 * Implementation Notes:
64 *
65 * Some terminology:
66 *
67 * index - index of the items that are currently visible
68 * position - index of the items in the cursor
69 */
70
71
72/**
Joe Fernandeza3aa8792017-04-24 16:16:29 -070073 * <p>Displays a vertically-scrollable collection of views, where each view is positioned
74 * immediatelybelow the previous view in the list. For a more modern, flexible, and performant
75 * approach to displaying lists, use {@link android.support.v7.widget.RecyclerView}.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 *
Joe Fernandeza3aa8792017-04-24 16:16:29 -070077 * <p>To display a list, you can include a list view in your layout XML file:</p>
78 *
79 * <pre>&lt;ListView
80 * android:id="@+id/list_view"
81 * android:layout_width="match_parent"
82 * android:layout_height="match_parent" /&gt;</pre>
83 *
84 * <p>A list view is an <a href="{@docRoot}guide/topics/ui/declaring-layout.html#AdapterViews">
85 * adapter view</a> that does not know the details, such as type and contents, of the views it
86 * contains. Instead list view requests views on demand from a {@link ListAdapter} as needed,
87 * such as to display new views as the user scrolls up or down.</p>
88 *
89 * <p>In order to display items in the list, call {@link #setAdapter(ListAdapter adapter)}
90 * to associate an adapter with the list. For a simple example, see the discussion of filling an
91 * adapter view with text in the
92 * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#FillingTheLayout">
93 * Layouts</a> guide.</p>
94 *
95 * <p>To display a more custom view for each item in your dataset, implement a ListAdapter.
96 * For example, extend {@link BaseAdapter} and create and configure the view for each data item in
97 * {@code getView(...)}:</p>
98 *
99 * <pre>private class MyAdapter extends BaseAdapter {
100 *
101 * // override other abstract methods here
102 *
103 * &#64;Override
104 * public View getView(int position, View convertView, ViewGroup container) {
105 * if (convertView == null) {
106 * convertView = getLayoutInflater().inflate(R.layout.list_item, container, false);
107 * }
108 *
109 * ((TextView) convertView.findViewById(android.R.id.text1))
110 * .setText(getItem(position));
111 * return convertView;
112 * }
113 * }</pre>
114 *
115 * <p class="note">ListView attempts to reuse view objects in order to improve performance and
116 * avoid a lag in response to user scrolls. To take advantage of this feature, check if the
117 * {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
118 * view object. See
119 * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
120 * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
121 *
122 * <p>For a more complete example of creating a custom adapter, see the
123 * <a href="{@docRoot}samples/CustomChoiceList/index.html">
124 * Custom Choice List</a> sample app.</p>
125 *
126 * <p>To specify an action when a user clicks or taps on a single list item, see
127 * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
128 * Handling click events</a>.</p>
129 *
130 * <p>To learn how to populate a list view with a CursorAdapter, see the discussion of filling an
131 * adapter view with text in the
132 * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#FillingTheLayout">
133 * Layouts</a> guide.
134 * See <a href="{@docRoot}guide/topics/ui/layout/listview.html">
135 * Using a Loader</a>
136 * to learn how to avoid blocking the main thread when using a cursor.</p>
137 *
138 * <p class="note">Note, many examples use {@link android.app.ListActivity ListActivity}
139 * or {@link android.app.ListFragment ListFragment}
140 * to display a list view. Instead, favor the more flexible approach when writing your own app:
141 * use a more generic Activity subclass or Fragment subclass and add a list view to the layout
142 * or view hierarchy directly. This approach gives you more direct control of the
143 * list view and adapter.</p>
Scott Main41ec6532010-08-19 16:57:07 -0700144 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 * @attr ref android.R.styleable#ListView_entries
146 * @attr ref android.R.styleable#ListView_divider
147 * @attr ref android.R.styleable#ListView_dividerHeight
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 * @attr ref android.R.styleable#ListView_headerDividersEnabled
149 * @attr ref android.R.styleable#ListView_footerDividersEnabled
150 */
Winson Chung499cb9f2010-07-16 11:18:17 -0700151@RemoteView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152public class ListView extends AbsListView {
Daichi Hironoa3e6a952017-05-09 18:17:34 +0900153 static final String TAG = "ListView";
154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 /**
156 * Used to indicate a no preference for a position type.
157 */
158 static final int NO_POSITION = -1;
159
160 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 * When arrow scrolling, ListView will never scroll more than this factor
162 * times the height of the list.
163 */
164 private static final float MAX_SCROLL_FACTOR = 0.33f;
165
166 /**
167 * When arrow scrolling, need a certain amount of pixels to preview next
168 * items. This is usually the fading edge, but if that is small enough,
169 * we want to make sure we preview at least this many pixels.
170 */
171 private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
172
173 /**
174 * A class that represents a fixed view in a list, for example a header at the top
175 * or a footer at the bottom.
176 */
177 public class FixedViewInfo {
178 /** The view to add to the list */
179 public View view;
180 /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
181 public Object data;
182 /** <code>true</code> if the fixed view should be selectable in the list */
183 public boolean isSelectable;
184 }
185
Mathew Inwood978c6e22018-08-21 15:58:55 +0100186 @UnsupportedAppUsage
Michael Kwan744be162016-07-22 18:37:31 -0700187 ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
Mathew Inwood978c6e22018-08-21 15:58:55 +0100188 @UnsupportedAppUsage
Michael Kwan744be162016-07-22 18:37:31 -0700189 ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190
Mathew Inwood978c6e22018-08-21 15:58:55 +0100191 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 Drawable mDivider;
Mathew Inwood31755f92018-12-20 13:53:36 +0000193 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 int mDividerHeight;
Adam Cohenfb603862010-12-17 12:03:17 -0800195
Adam Powell637d3372010-08-25 14:37:03 -0700196 Drawable mOverScrollHeader;
197 Drawable mOverScrollFooter;
198
Romain Guy24443ea2009-05-11 11:56:30 -0700199 private boolean mIsCacheColorOpaque;
200 private boolean mDividerIsOpaque;
Romain Guy24443ea2009-05-11 11:56:30 -0700201
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 private boolean mHeaderDividersEnabled;
203 private boolean mFooterDividersEnabled;
204
Mathew Inwood978c6e22018-08-21 15:58:55 +0100205 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 private boolean mAreAllItemsSelectable = true;
207
208 private boolean mItemsCanFocus = false;
209
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 // used for temporary calculations.
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700211 private final Rect mTempRect = new Rect();
Romain Guya02903f2009-05-23 13:26:46 -0700212 private Paint mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213
214 // the single allocated result per list view; kinda cheesey but avoids
215 // allocating these thingies too often.
Romain Guy9c3184cc2010-02-25 17:32:54 -0800216 private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217
Adam Powell9bf3c122010-02-26 11:32:07 -0800218 // Keeps focused children visible through resizes
219 private FocusSelector mFocusSelector;
Adam Powell8350f7d2010-07-28 14:27:28 -0700220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 public ListView(Context context) {
222 this(context, null);
223 }
224
225 public ListView(Context context, AttributeSet attrs) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700226 this(context, attrs, R.attr.listViewStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 }
228
Alan Viverette617feb92013-09-09 18:09:13 -0700229 public ListView(Context context, AttributeSet attrs, int defStyleAttr) {
230 this(context, attrs, defStyleAttr, 0);
231 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232
Alan Viverette617feb92013-09-09 18:09:13 -0700233 public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
234 super(context, attrs, defStyleAttr, defStyleRes);
235
236 final TypedArray a = context.obtainStyledAttributes(
Alan Viverette7b2f8642015-06-01 10:30:21 -0700237 attrs, R.styleable.ListView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238
Alan Viverette7b2f8642015-06-01 10:30:21 -0700239 final CharSequence[] entries = a.getTextArray(R.styleable.ListView_entries);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 if (entries != null) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700241 setAdapter(new ArrayAdapter<>(context, R.layout.simple_list_item_1, entries));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 }
243
Alan Viverette7b2f8642015-06-01 10:30:21 -0700244 final Drawable d = a.getDrawable(R.styleable.ListView_divider);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 if (d != null) {
Alan Viverette7b2f8642015-06-01 10:30:21 -0700246 // Use an implicit divider height which may be explicitly
247 // overridden by android:dividerHeight further down.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 setDivider(d);
249 }
Alan Viverette7b2f8642015-06-01 10:30:21 -0700250
251 final Drawable osHeader = a.getDrawable(R.styleable.ListView_overScrollHeader);
Adam Powell637d3372010-08-25 14:37:03 -0700252 if (osHeader != null) {
253 setOverscrollHeader(osHeader);
254 }
255
Alan Viverette7b2f8642015-06-01 10:30:21 -0700256 final Drawable osFooter = a.getDrawable(R.styleable.ListView_overScrollFooter);
Adam Powell637d3372010-08-25 14:37:03 -0700257 if (osFooter != null) {
258 setOverscrollFooter(osFooter);
259 }
260
Alan Viverette7b2f8642015-06-01 10:30:21 -0700261 // Use an explicit divider height, if specified.
262 if (a.hasValueOrEmpty(R.styleable.ListView_dividerHeight)) {
263 final int dividerHeight = a.getDimensionPixelSize(
264 R.styleable.ListView_dividerHeight, 0);
265 if (dividerHeight != 0) {
266 setDividerHeight(dividerHeight);
267 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 }
269
270 mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
271 mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
272
273 a.recycle();
274 }
275
276 /**
277 * @return The maximum amount a list view will scroll in response to
278 * an arrow event.
279 */
280 public int getMaxScrollAmount() {
281 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
282 }
283
284 /**
285 * Make sure views are touching the top or bottom edge, as appropriate for
286 * our gravity
287 */
288 private void adjustViewsUpOrDown() {
289 final int childCount = getChildCount();
290 int delta;
291
292 if (childCount > 0) {
293 View child;
294
295 if (!mStackFromBottom) {
296 // Uh-oh -- we came up short. Slide all views up to make them
297 // align with the top
298 child = getChildAt(0);
299 delta = child.getTop() - mListPadding.top;
300 if (mFirstPosition != 0) {
301 // It's OK to have some space above the first item if it is
302 // part of the vertical spacing
303 delta -= mDividerHeight;
304 }
305 if (delta < 0) {
306 // We only are looking to see if we are too low, not too high
307 delta = 0;
308 }
309 } else {
310 // we are too high, slide all views down to align with bottom
311 child = getChildAt(childCount - 1);
312 delta = child.getBottom() - (getHeight() - mListPadding.bottom);
313
314 if (mFirstPosition + childCount < mItemCount) {
315 // It's OK to have some space below the last item if it is
316 // part of the vertical spacing
317 delta += mDividerHeight;
318 }
319
320 if (delta > 0) {
321 delta = 0;
322 }
323 }
324
325 if (delta != 0) {
326 offsetChildrenTopAndBottom(-delta);
327 }
328 }
329 }
330
331 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700332 * 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 -0800333 * called more than once, the views will appear in the order they were
334 * added. Views added using this call can take focus if they want.
335 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700336 * Note: When first introduced, this method could only be called before
337 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700338 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700339 * called at any time. If the ListView's adapter does not extend
340 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
341 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 *
343 * @param v The view to add.
344 * @param data Data to associate with this view
345 * @param isSelectable whether the item is selectable
346 */
347 public void addHeaderView(View v, Object data, boolean isSelectable) {
Daichi Hironoa3e6a952017-05-09 18:17:34 +0900348 if (v.getParent() != null && v.getParent() != this) {
349 if (Log.isLoggable(TAG, Log.WARN)) {
350 Log.w(TAG, "The specified child already has a parent. "
351 + "You must call removeView() on the child's parent first.");
352 }
353 }
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700354 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 info.view = v;
356 info.data = data;
357 info.isSelectable = isSelectable;
358 mHeaderViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800359 mAreAllItemsSelectable &= isSelectable;
Marco Nelissen22c04a32011-04-19 14:07:55 -0700360
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700361 // Wrap the adapter if it wasn't already wrapped.
362 if (mAdapter != null) {
363 if (!(mAdapter instanceof HeaderViewListAdapter)) {
Michael Kwan744be162016-07-22 18:37:31 -0700364 wrapHeaderListAdapterInternal();
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700365 }
366
367 // In the case of re-adding a header view, or adding one later on,
368 // we need to notify the observer.
369 if (mDataSetObserver != null) {
370 mDataSetObserver.onChanged();
371 }
Marco Nelissen22c04a32011-04-19 14:07:55 -0700372 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 }
374
375 /**
376 * Add a fixed view to appear at the top of the list. If addHeaderView is
377 * called more than once, the views will appear in the order they were
378 * added. Views added using this call can take focus if they want.
379 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700380 * Note: When first introduced, this method could only be called before
381 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700382 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700383 * called at any time. If the ListView's adapter does not extend
384 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
385 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 *
387 * @param v The view to add.
388 */
389 public void addHeaderView(View v) {
390 addHeaderView(v, null, true);
391 }
392
393 @Override
394 public int getHeaderViewsCount() {
395 return mHeaderViewInfos.size();
396 }
397
398 /**
399 * Removes a previously-added header view.
400 *
401 * @param v The view to remove
402 * @return true if the view was removed, false if the view was not a header
403 * view
404 */
405 public boolean removeHeaderView(View v) {
406 if (mHeaderViewInfos.size() > 0) {
407 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700408 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700409 if (mDataSetObserver != null) {
410 mDataSetObserver.onChanged();
411 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 result = true;
413 }
414 removeFixedViewInfo(v, mHeaderViewInfos);
415 return result;
416 }
417 return false;
418 }
419
420 private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
421 int len = where.size();
422 for (int i = 0; i < len; ++i) {
423 FixedViewInfo info = where.get(i);
424 if (info.view == v) {
425 where.remove(i);
426 break;
427 }
428 }
429 }
430
431 /**
432 * Add a fixed view to appear at the bottom of the list. If addFooterView is
433 * called more than once, the views will appear in the order they were
434 * added. Views added using this call can take focus if they want.
435 * <p>
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700436 * Note: When first introduced, this method could only be called before
437 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700438 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700439 * called at any time. If the ListView's adapter does not extend
440 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
441 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 *
443 * @param v The view to add.
444 * @param data Data to associate with this view
445 * @param isSelectable true if the footer view can be selected
446 */
447 public void addFooterView(View v, Object data, boolean isSelectable) {
Daichi Hironoa3e6a952017-05-09 18:17:34 +0900448 if (v.getParent() != null && v.getParent() != this) {
449 if (Log.isLoggable(TAG, Log.WARN)) {
450 Log.w(TAG, "The specified child already has a parent. "
451 + "You must call removeView() on the child's parent first.");
452 }
453 }
454
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700455 final FixedViewInfo info = new FixedViewInfo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 info.view = v;
457 info.data = data;
458 info.isSelectable = isSelectable;
459 mFooterViewInfos.add(info);
Alan Viverette20cc6052013-11-15 15:56:38 -0800460 mAreAllItemsSelectable &= isSelectable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700462 // Wrap the adapter if it wasn't already wrapped.
463 if (mAdapter != null) {
464 if (!(mAdapter instanceof HeaderViewListAdapter)) {
Michael Kwan744be162016-07-22 18:37:31 -0700465 wrapHeaderListAdapterInternal();
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700466 }
467
468 // In the case of re-adding a footer view, or adding one later on,
469 // we need to notify the observer.
470 if (mDataSetObserver != null) {
471 mDataSetObserver.onChanged();
472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 }
474 }
475
476 /**
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700477 * Add a fixed view to appear at the bottom of the list. If addFooterView is
478 * called more than once, the views will appear in the order they were
479 * added. Views added using this call can take focus if they want.
480 * <p>
481 * Note: When first introduced, this method could only be called before
482 * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
Chet Haasee8222dd2013-09-05 07:44:18 -0700483 * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
Alan Viverette72e2f2a2013-06-12 12:56:29 -0700484 * called at any time. If the ListView's adapter does not extend
485 * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
486 * instance of {@link WrapperListAdapter}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 *
488 * @param v The view to add.
489 */
490 public void addFooterView(View v) {
491 addFooterView(v, null, true);
492 }
493
494 @Override
495 public int getFooterViewsCount() {
496 return mFooterViewInfos.size();
497 }
498
499 /**
500 * Removes a previously-added footer view.
501 *
502 * @param v The view to remove
503 * @return
504 * true if the view was removed, false if the view was not a footer view
505 */
506 public boolean removeFooterView(View v) {
507 if (mFooterViewInfos.size() > 0) {
508 boolean result = false;
Adam Powell247a0f02011-09-13 13:11:29 -0700509 if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
Marco Nelissen22c04a32011-04-19 14:07:55 -0700510 if (mDataSetObserver != null) {
511 mDataSetObserver.onChanged();
512 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 result = true;
514 }
515 removeFixedViewInfo(v, mFooterViewInfos);
516 return result;
517 }
518 return false;
519 }
520
521 /**
522 * Returns the adapter currently in use in this ListView. The returned adapter
523 * might not be the same adapter passed to {@link #setAdapter(ListAdapter)} but
524 * might be a {@link WrapperListAdapter}.
525 *
526 * @return The adapter currently used to display data in this ListView.
527 *
528 * @see #setAdapter(ListAdapter)
529 */
530 @Override
531 public ListAdapter getAdapter() {
532 return mAdapter;
533 }
534
535 /**
Winson Chung499cb9f2010-07-16 11:18:17 -0700536 * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
537 * through the specified intent.
538 * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
539 */
Sunny Goyal5c022632016-02-17 16:30:41 -0800540 @android.view.RemotableViewMethod(asyncImpl="setRemoteViewsAdapterAsync")
Winson Chung499cb9f2010-07-16 11:18:17 -0700541 public void setRemoteViewsAdapter(Intent intent) {
542 super.setRemoteViewsAdapter(intent);
543 }
544
545 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 * Sets the data behind this ListView.
547 *
548 * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
549 * depending on the ListView features currently in use. For instance, adding
550 * headers and/or footers will cause the adapter to be wrapped.
551 *
552 * @param adapter The ListAdapter which is responsible for maintaining the
553 * data backing this list and for producing a view to represent an
554 * item in that data set.
555 *
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700556 * @see #getAdapter()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 */
558 @Override
559 public void setAdapter(ListAdapter adapter) {
Romain Guydf36b052010-05-19 21:13:20 -0700560 if (mAdapter != null && mDataSetObserver != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 mAdapter.unregisterDataSetObserver(mDataSetObserver);
562 }
563
564 resetList();
565 mRecycler.clear();
566
567 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
Michael Kwan744be162016-07-22 18:37:31 -0700568 mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569 } else {
570 mAdapter = adapter;
571 }
572
573 mOldSelectedPosition = INVALID_POSITION;
574 mOldSelectedRowId = INVALID_ROW_ID;
Adam Powellf343e1b2010-08-13 18:27:04 -0700575
576 // AbsListView#setAdapter will update choice mode states.
577 super.setAdapter(adapter);
578
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 if (mAdapter != null) {
580 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
581 mOldItemCount = mItemCount;
582 mItemCount = mAdapter.getCount();
583 checkFocus();
584
585 mDataSetObserver = new AdapterDataSetObserver();
586 mAdapter.registerDataSetObserver(mDataSetObserver);
587
588 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
589
590 int position;
591 if (mStackFromBottom) {
592 position = lookForSelectablePosition(mItemCount - 1, false);
593 } else {
594 position = lookForSelectablePosition(0, true);
595 }
596 setSelectedPositionInt(position);
597 setNextSelectedPositionInt(position);
598
599 if (mItemCount == 0) {
600 // Nothing selected
601 checkSelectionChanged();
602 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 } else {
604 mAreAllItemsSelectable = true;
605 checkFocus();
606 // Nothing selected
607 checkSelectionChanged();
608 }
609
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 requestLayout();
611 }
612
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 /**
614 * The list is empty. Clear everything out.
615 */
616 @Override
617 void resetList() {
Romain Guy2e447d42009-04-28 18:01:24 -0700618 // The parent's resetList() will remove all views from the layout so we need to
619 // cleanup the state of our footers and headers
620 clearRecycledState(mHeaderViewInfos);
621 clearRecycledState(mFooterViewInfos);
622
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 super.resetList();
Romain Guy2e447d42009-04-28 18:01:24 -0700624
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 mLayoutMode = LAYOUT_NORMAL;
626 }
627
Romain Guy2e447d42009-04-28 18:01:24 -0700628 private void clearRecycledState(ArrayList<FixedViewInfo> infos) {
629 if (infos != null) {
630 final int count = infos.size();
631
632 for (int i = 0; i < count; i++) {
633 final View child = infos.get(i).view;
Daichi Hironob8905232017-08-28 10:29:27 +0900634 final ViewGroup.LayoutParams params = child.getLayoutParams();
635 if (checkLayoutParams(params)) {
636 ((LayoutParams) params).recycledHeaderFooter = false;
Romain Guy2e447d42009-04-28 18:01:24 -0700637 }
638 }
639 }
640 }
641
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 /**
643 * @return Whether the list needs to show the top fading edge
644 */
645 private boolean showingTopFadingEdge() {
646 final int listTop = mScrollY + mListPadding.top;
647 return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
648 }
649
650 /**
651 * @return Whether the list needs to show the bottom fading edge
652 */
653 private boolean showingBottomFadingEdge() {
654 final int childCount = getChildCount();
655 final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
656 final int lastVisiblePosition = mFirstPosition + childCount - 1;
657
658 final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
659
660 return (lastVisiblePosition < mItemCount - 1)
661 || (bottomOfBottomChild < listBottom);
662 }
663
664
665 @Override
666 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
667
668 int rectTopWithinChild = rect.top;
669
670 // offset so rect is in coordinates of the this view
671 rect.offset(child.getLeft(), child.getTop());
672 rect.offset(-child.getScrollX(), -child.getScrollY());
673
674 final int height = getHeight();
675 int listUnfadedTop = getScrollY();
676 int listUnfadedBottom = listUnfadedTop + height;
677 final int fadingEdge = getVerticalFadingEdgeLength();
678
679 if (showingTopFadingEdge()) {
680 // leave room for top fading edge as long as rect isn't at very top
681 if ((mSelectedPosition > 0) || (rectTopWithinChild > fadingEdge)) {
682 listUnfadedTop += fadingEdge;
683 }
684 }
685
686 int childCount = getChildCount();
687 int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
688
689 if (showingBottomFadingEdge()) {
690 // leave room for bottom fading edge as long as rect isn't at very bottom
691 if ((mSelectedPosition < mItemCount - 1)
692 || (rect.bottom < (bottomOfBottomChild - fadingEdge))) {
693 listUnfadedBottom -= fadingEdge;
694 }
695 }
696
697 int scrollYDelta = 0;
698
699 if (rect.bottom > listUnfadedBottom && rect.top > listUnfadedTop) {
700 // need to MOVE DOWN to get it in view: move down just enough so
701 // that the entire rectangle is in view (or at least the first
702 // screen size chunk).
703
704 if (rect.height() > height) {
705 // just enough to get screen size chunk on
706 scrollYDelta += (rect.top - listUnfadedTop);
707 } else {
708 // get entire rect at bottom of screen
709 scrollYDelta += (rect.bottom - listUnfadedBottom);
710 }
711
712 // make sure we aren't scrolling beyond the end of our children
713 int distanceToBottom = bottomOfBottomChild - listUnfadedBottom;
714 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
715 } else if (rect.top < listUnfadedTop && rect.bottom < listUnfadedBottom) {
716 // need to MOVE UP to get it in view: move up just enough so that
717 // entire rectangle is in view (or at least the first screen
718 // size chunk of it).
719
720 if (rect.height() > height) {
721 // screen size chunk
722 scrollYDelta -= (listUnfadedBottom - rect.bottom);
723 } else {
724 // entire rect at top
725 scrollYDelta -= (listUnfadedTop - rect.top);
726 }
727
728 // make sure we aren't scrolling any further than the top our children
729 int top = getChildAt(0).getTop();
730 int deltaToTop = top - listUnfadedTop;
731 scrollYDelta = Math.max(scrollYDelta, deltaToTop);
732 }
733
734 final boolean scroll = scrollYDelta != 0;
735 if (scroll) {
736 scrollListItemsBy(-scrollYDelta);
Dianne Hackborn079e2352010-10-18 17:02:43 -0700737 positionSelector(INVALID_POSITION, child);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 mSelectedTop = child.getTop();
739 invalidate();
740 }
741 return scroll;
742 }
743
744 /**
745 * {@inheritDoc}
746 */
747 @Override
748 void fillGap(boolean down) {
749 final int count = getChildCount();
750 if (down) {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800751 int paddingTop = 0;
752 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
753 paddingTop = getListPaddingTop();
754 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800755 final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800756 paddingTop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 fillDown(mFirstPosition + count, startOffset);
758 correctTooHigh(getChildCount());
759 } else {
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800760 int paddingBottom = 0;
761 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
762 paddingBottom = getListPaddingBottom();
763 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800765 getHeight() - paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766 fillUp(mFirstPosition - 1, startOffset);
767 correctTooLow(getChildCount());
768 }
769 }
770
771 /**
772 * Fills the list from pos down to the end of the list view.
773 *
774 * @param pos The first position to put in the list
775 *
776 * @param nextTop The location where the top of the item associated with pos
777 * should be drawn
778 *
779 * @return The view that is currently selected, if it happens to be in the
780 * range that we draw.
781 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100782 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 private View fillDown(int pos, int nextTop) {
784 View selectedView = null;
785
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800786 int end = (mBottom - mTop);
787 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
788 end -= mListPadding.bottom;
789 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790
791 while (nextTop < end && pos < mItemCount) {
792 // is this the selected item?
793 boolean selected = pos == mSelectedPosition;
794 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
795
796 nextTop = child.getBottom() + mDividerHeight;
797 if (selected) {
798 selectedView = child;
799 }
800 pos++;
801 }
802
Adam Cohenb9673922012-01-05 13:58:47 -0800803 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800804 return selectedView;
805 }
806
807 /**
808 * Fills the list from pos up to the top of the list view.
809 *
810 * @param pos The first position to put in the list
811 *
812 * @param nextBottom The location where the bottom of the item associated
813 * with pos should be drawn
814 *
815 * @return The view that is currently selected
816 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100817 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 private View fillUp(int pos, int nextBottom) {
819 View selectedView = null;
820
Adam Powell8c3e0fc2010-11-17 15:13:51 -0800821 int end = 0;
822 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
823 end = mListPadding.top;
824 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825
826 while (nextBottom > end && pos >= 0) {
827 // is this the selected item?
828 boolean selected = pos == mSelectedPosition;
829 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
830 nextBottom = child.getTop() - mDividerHeight;
831 if (selected) {
832 selectedView = child;
833 }
834 pos--;
835 }
836
837 mFirstPosition = pos + 1;
Adam Cohenb9673922012-01-05 13:58:47 -0800838 setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 return selectedView;
840 }
841
842 /**
843 * Fills the list from top to bottom, starting with mFirstPosition
844 *
845 * @param nextTop The location where the top of the first item should be
846 * drawn
847 *
848 * @return The view that is currently selected
849 */
850 private View fillFromTop(int nextTop) {
851 mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
852 mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
853 if (mFirstPosition < 0) {
854 mFirstPosition = 0;
855 }
856 return fillDown(mFirstPosition, nextTop);
857 }
858
859
860 /**
861 * Put mSelectedPosition in the middle of the screen and then build up and
862 * down from there. This method forces mSelectedPosition to the center.
863 *
864 * @param childrenTop Top of the area in which children can be drawn, as
865 * measured in pixels
866 * @param childrenBottom Bottom of the area in which children can be drawn,
867 * as measured in pixels
868 * @return Currently selected view
869 */
870 private View fillFromMiddle(int childrenTop, int childrenBottom) {
871 int height = childrenBottom - childrenTop;
872
873 int position = reconcileSelectedPosition();
874
875 View sel = makeAndAddView(position, childrenTop, true,
876 mListPadding.left, true);
877 mFirstPosition = position;
878
879 int selHeight = sel.getMeasuredHeight();
880 if (selHeight <= height) {
881 sel.offsetTopAndBottom((height - selHeight) / 2);
882 }
883
884 fillAboveAndBelow(sel, position);
885
886 if (!mStackFromBottom) {
887 correctTooHigh(getChildCount());
888 } else {
889 correctTooLow(getChildCount());
890 }
891
892 return sel;
893 }
894
895 /**
896 * Once the selected view as been placed, fill up the visible area above and
897 * below it.
898 *
899 * @param sel The selected view
900 * @param position The position corresponding to sel
901 */
902 private void fillAboveAndBelow(View sel, int position) {
903 final int dividerHeight = mDividerHeight;
904 if (!mStackFromBottom) {
905 fillUp(position - 1, sel.getTop() - dividerHeight);
906 adjustViewsUpOrDown();
907 fillDown(position + 1, sel.getBottom() + dividerHeight);
908 } else {
909 fillDown(position + 1, sel.getBottom() + dividerHeight);
910 adjustViewsUpOrDown();
911 fillUp(position - 1, sel.getTop() - dividerHeight);
912 }
913 }
914
915
916 /**
917 * Fills the grid based on positioning the new selection at a specific
918 * location. The selection may be moved so that it does not intersect the
919 * faded edges. The grid is then filled upwards and downwards from there.
920 *
921 * @param selectedTop Where the selected item should be
922 * @param childrenTop Where to start drawing children
923 * @param childrenBottom Last pixel where children can be drawn
924 * @return The view that currently has selection
925 */
926 private View fillFromSelection(int selectedTop, int childrenTop, int childrenBottom) {
927 int fadingEdgeLength = getVerticalFadingEdgeLength();
928 final int selectedPosition = mSelectedPosition;
929
930 View sel;
931
932 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
933 selectedPosition);
934 final int bottomSelectionPixel = getBottomSelectionPixel(childrenBottom, fadingEdgeLength,
935 selectedPosition);
936
937 sel = makeAndAddView(selectedPosition, selectedTop, true, mListPadding.left, true);
938
939
940 // Some of the newly selected item extends below the bottom of the list
941 if (sel.getBottom() > bottomSelectionPixel) {
942 // Find space available above the selection into which we can scroll
943 // upwards
944 final int spaceAbove = sel.getTop() - topSelectionPixel;
945
946 // Find space required to bring the bottom of the selected item
947 // fully into view
948 final int spaceBelow = sel.getBottom() - bottomSelectionPixel;
949 final int offset = Math.min(spaceAbove, spaceBelow);
950
951 // Now offset the selected item to get it into view
952 sel.offsetTopAndBottom(-offset);
953 } else if (sel.getTop() < topSelectionPixel) {
954 // Find space required to bring the top of the selected item fully
955 // into view
956 final int spaceAbove = topSelectionPixel - sel.getTop();
957
958 // Find space available below the selection into which we can scroll
959 // downwards
960 final int spaceBelow = bottomSelectionPixel - sel.getBottom();
961 final int offset = Math.min(spaceAbove, spaceBelow);
962
963 // Offset the selected item to get it into view
964 sel.offsetTopAndBottom(offset);
965 }
966
967 // Fill in views above and below
968 fillAboveAndBelow(sel, selectedPosition);
969
970 if (!mStackFromBottom) {
971 correctTooHigh(getChildCount());
972 } else {
973 correctTooLow(getChildCount());
974 }
975
976 return sel;
977 }
978
979 /**
980 * Calculate the bottom-most pixel we can draw the selection into
981 *
982 * @param childrenBottom Bottom pixel were children can be drawn
983 * @param fadingEdgeLength Length of the fading edge in pixels, if present
984 * @param selectedPosition The position that will be selected
985 * @return The bottom-most pixel we can draw the selection into
986 */
987 private int getBottomSelectionPixel(int childrenBottom, int fadingEdgeLength,
988 int selectedPosition) {
989 int bottomSelectionPixel = childrenBottom;
990 if (selectedPosition != mItemCount - 1) {
991 bottomSelectionPixel -= fadingEdgeLength;
992 }
993 return bottomSelectionPixel;
994 }
995
996 /**
997 * Calculate the top-most pixel we can draw the selection into
998 *
999 * @param childrenTop Top pixel were children can be drawn
1000 * @param fadingEdgeLength Length of the fading edge in pixels, if present
1001 * @param selectedPosition The position that will be selected
1002 * @return The top-most pixel we can draw the selection into
1003 */
1004 private int getTopSelectionPixel(int childrenTop, int fadingEdgeLength, int selectedPosition) {
1005 // first pixel we can draw the selection into
1006 int topSelectionPixel = childrenTop;
1007 if (selectedPosition > 0) {
1008 topSelectionPixel += fadingEdgeLength;
1009 }
1010 return topSelectionPixel;
1011 }
1012
Winson Chung499cb9f2010-07-16 11:18:17 -07001013 /**
1014 * Smoothly scroll to the specified adapter position. The view will
1015 * scroll such that the indicated position is displayed.
1016 * @param position Scroll to this adapter position.
1017 */
1018 @android.view.RemotableViewMethod
1019 public void smoothScrollToPosition(int position) {
1020 super.smoothScrollToPosition(position);
1021 }
1022
1023 /**
1024 * Smoothly scroll to the specified adapter position offset. The view will
1025 * scroll such that the indicated position is displayed.
1026 * @param offset The amount to offset from the adapter position to scroll to.
1027 */
1028 @android.view.RemotableViewMethod
1029 public void smoothScrollByOffset(int offset) {
1030 super.smoothScrollByOffset(offset);
1031 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032
1033 /**
1034 * Fills the list based on positioning the new selection relative to the old
1035 * selection. The new selection will be placed at, above, or below the
1036 * location of the new selection depending on how the selection is moving.
1037 * The selection will then be pinned to the visible part of the screen,
1038 * excluding the edges that are faded. The list is then filled upwards and
1039 * downwards from there.
1040 *
1041 * @param oldSel The old selected view. Useful for trying to put the new
1042 * selection in the same place
1043 * @param newSel The view that is to become selected. Useful for trying to
1044 * put the new selection in the same place
1045 * @param delta Which way we are moving
1046 * @param childrenTop Where to start drawing children
1047 * @param childrenBottom Last pixel where children can be drawn
1048 * @return The view that currently has selection
1049 */
1050 private View moveSelection(View oldSel, View newSel, int delta, int childrenTop,
1051 int childrenBottom) {
1052 int fadingEdgeLength = getVerticalFadingEdgeLength();
1053 final int selectedPosition = mSelectedPosition;
1054
1055 View sel;
1056
1057 final int topSelectionPixel = getTopSelectionPixel(childrenTop, fadingEdgeLength,
1058 selectedPosition);
1059 final int bottomSelectionPixel = getBottomSelectionPixel(childrenTop, fadingEdgeLength,
1060 selectedPosition);
1061
1062 if (delta > 0) {
1063 /*
1064 * Case 1: Scrolling down.
1065 */
1066
1067 /*
1068 * Before After
1069 * | | | |
1070 * +-------+ +-------+
1071 * | A | | A |
1072 * | 1 | => +-------+
1073 * +-------+ | B |
1074 * | B | | 2 |
1075 * +-------+ +-------+
1076 * | | | |
1077 *
1078 * Try to keep the top of the previously selected item where it was.
1079 * oldSel = A
1080 * sel = B
1081 */
1082
1083 // Put oldSel (A) where it belongs
1084 oldSel = makeAndAddView(selectedPosition - 1, oldSel.getTop(), true,
1085 mListPadding.left, false);
1086
1087 final int dividerHeight = mDividerHeight;
1088
1089 // Now put the new selection (B) below that
1090 sel = makeAndAddView(selectedPosition, oldSel.getBottom() + dividerHeight, true,
1091 mListPadding.left, true);
1092
1093 // Some of the newly selected item extends below the bottom of the list
1094 if (sel.getBottom() > bottomSelectionPixel) {
1095
1096 // Find space available above the selection into which we can scroll upwards
1097 int spaceAbove = sel.getTop() - topSelectionPixel;
1098
1099 // Find space required to bring the bottom of the selected item fully into view
1100 int spaceBelow = sel.getBottom() - bottomSelectionPixel;
1101
1102 // Don't scroll more than half the height of the list
1103 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1104 int offset = Math.min(spaceAbove, spaceBelow);
1105 offset = Math.min(offset, halfVerticalSpace);
1106
1107 // We placed oldSel, so offset that item
1108 oldSel.offsetTopAndBottom(-offset);
1109 // Now offset the selected item to get it into view
1110 sel.offsetTopAndBottom(-offset);
1111 }
1112
1113 // Fill in views above and below
1114 if (!mStackFromBottom) {
1115 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1116 adjustViewsUpOrDown();
1117 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1118 } else {
1119 fillDown(mSelectedPosition + 1, sel.getBottom() + dividerHeight);
1120 adjustViewsUpOrDown();
1121 fillUp(mSelectedPosition - 2, sel.getTop() - dividerHeight);
1122 }
1123 } else if (delta < 0) {
1124 /*
1125 * Case 2: Scrolling up.
1126 */
1127
1128 /*
1129 * Before After
1130 * | | | |
1131 * +-------+ +-------+
1132 * | A | | A |
1133 * +-------+ => | 1 |
1134 * | B | +-------+
1135 * | 2 | | B |
1136 * +-------+ +-------+
1137 * | | | |
1138 *
1139 * Try to keep the top of the item about to become selected where it was.
1140 * newSel = A
1141 * olSel = B
1142 */
1143
1144 if (newSel != null) {
1145 // Try to position the top of newSel (A) where it was before it was selected
1146 sel = makeAndAddView(selectedPosition, newSel.getTop(), true, mListPadding.left,
1147 true);
1148 } else {
1149 // If (A) was not on screen and so did not have a view, position
1150 // it above the oldSel (B)
1151 sel = makeAndAddView(selectedPosition, oldSel.getTop(), false, mListPadding.left,
1152 true);
1153 }
1154
1155 // Some of the newly selected item extends above the top of the list
1156 if (sel.getTop() < topSelectionPixel) {
1157 // Find space required to bring the top of the selected item fully into view
1158 int spaceAbove = topSelectionPixel - sel.getTop();
1159
1160 // Find space available below the selection into which we can scroll downwards
1161 int spaceBelow = bottomSelectionPixel - sel.getBottom();
1162
1163 // Don't scroll more than half the height of the list
1164 int halfVerticalSpace = (childrenBottom - childrenTop) / 2;
1165 int offset = Math.min(spaceAbove, spaceBelow);
1166 offset = Math.min(offset, halfVerticalSpace);
1167
1168 // Offset the selected item to get it into view
1169 sel.offsetTopAndBottom(offset);
1170 }
1171
1172 // Fill in views above and below
1173 fillAboveAndBelow(sel, selectedPosition);
1174 } else {
1175
1176 int oldTop = oldSel.getTop();
1177
1178 /*
1179 * Case 3: Staying still
1180 */
1181 sel = makeAndAddView(selectedPosition, oldTop, true, mListPadding.left, true);
1182
1183 // We're staying still...
1184 if (oldTop < childrenTop) {
1185 // ... but the top of the old selection was off screen.
1186 // (This can happen if the data changes size out from under us)
1187 int newBottom = sel.getBottom();
1188 if (newBottom < childrenTop + 20) {
1189 // Not enough visible -- bring it onscreen
1190 sel.offsetTopAndBottom(childrenTop - sel.getTop());
1191 }
1192 }
1193
1194 // Fill in views above and below
1195 fillAboveAndBelow(sel, selectedPosition);
1196 }
1197
1198 return sel;
1199 }
1200
Adam Powell9bf3c122010-02-26 11:32:07 -08001201 private class FocusSelector implements Runnable {
Yigit Boyar03633412016-03-24 17:44:28 -07001202 // the selector is waiting to set selection on the list view
1203 private static final int STATE_SET_SELECTION = 1;
1204 // the selector set the selection on the list view, waiting for a layoutChildren pass
1205 private static final int STATE_WAIT_FOR_LAYOUT = 2;
1206 // the selector's selection has been honored and it is waiting to request focus on the
1207 // target child.
1208 private static final int STATE_REQUEST_FOCUS = 3;
1209
1210 private int mAction;
Adam Powell9bf3c122010-02-26 11:32:07 -08001211 private int mPosition;
1212 private int mPositionTop;
Yigit Boyar03633412016-03-24 17:44:28 -07001213
1214 FocusSelector setupForSetSelection(int position, int top) {
Adam Powell9bf3c122010-02-26 11:32:07 -08001215 mPosition = position;
1216 mPositionTop = top;
Yigit Boyar03633412016-03-24 17:44:28 -07001217 mAction = STATE_SET_SELECTION;
Adam Powell9bf3c122010-02-26 11:32:07 -08001218 return this;
1219 }
Yigit Boyar03633412016-03-24 17:44:28 -07001220
Adam Powell9bf3c122010-02-26 11:32:07 -08001221 public void run() {
Yigit Boyar03633412016-03-24 17:44:28 -07001222 if (mAction == STATE_SET_SELECTION) {
1223 setSelectionFromTop(mPosition, mPositionTop);
1224 mAction = STATE_WAIT_FOR_LAYOUT;
1225 } else if (mAction == STATE_REQUEST_FOCUS) {
1226 final int childIndex = mPosition - mFirstPosition;
1227 final View child = getChildAt(childIndex);
1228 if (child != null) {
1229 child.requestFocus();
1230 }
1231 mAction = -1;
1232 }
1233 }
1234
1235 @Nullable Runnable setupFocusIfValid(int position) {
1236 if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) {
1237 return null;
1238 }
1239 mAction = STATE_REQUEST_FOCUS;
1240 return this;
1241 }
1242
1243 void onLayoutComplete() {
1244 if (mAction == STATE_WAIT_FOR_LAYOUT) {
1245 mAction = -1;
1246 }
Adam Powell9bf3c122010-02-26 11:32:07 -08001247 }
1248 }
Yigit Boyar03633412016-03-24 17:44:28 -07001249
1250 @Override
1251 protected void onDetachedFromWindow() {
1252 if (mFocusSelector != null) {
1253 removeCallbacks(mFocusSelector);
1254 mFocusSelector = null;
1255 }
1256 super.onDetachedFromWindow();
1257 }
1258
Adam Powell9bf3c122010-02-26 11:32:07 -08001259 @Override
1260 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1261 if (getChildCount() > 0) {
1262 View focusedChild = getFocusedChild();
1263 if (focusedChild != null) {
1264 final int childPosition = mFirstPosition + indexOfChild(focusedChild);
1265 final int childBottom = focusedChild.getBottom();
1266 final int offset = Math.max(0, childBottom - (h - mPaddingTop));
1267 final int top = focusedChild.getTop() - offset;
1268 if (mFocusSelector == null) {
1269 mFocusSelector = new FocusSelector();
1270 }
Yigit Boyar03633412016-03-24 17:44:28 -07001271 post(mFocusSelector.setupForSetSelection(childPosition, top));
Adam Powell9bf3c122010-02-26 11:32:07 -08001272 }
1273 }
1274 super.onSizeChanged(w, h, oldw, oldh);
1275 }
1276
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001277 @Override
1278 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1279 // Sets up mListPadding
1280 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1281
Alan Viverette2ea32922015-06-26 13:31:50 -07001282 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
1283 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
1285 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
1286
1287 int childWidth = 0;
1288 int childHeight = 0;
Dianne Hackborn189ee182010-12-02 21:48:53 -08001289 int childState = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290
1291 mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
Alan Viverette2ea32922015-06-26 13:31:50 -07001292 if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
1293 || heightMode == MeasureSpec.UNSPECIFIED)) {
Romain Guy21875052010-01-06 18:48:08 -08001294 final View child = obtainView(0, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001295
Alan Viverette2ea32922015-06-26 13:31:50 -07001296 // Lay out child directly against the parent measure spec so that
Chet Haase4610eef2015-12-03 07:38:11 -08001297 // we can obtain exected minimum width and height.
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001298 measureScrapChild(child, 0, widthMeasureSpec, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001299
1300 childWidth = child.getMeasuredWidth();
1301 childHeight = child.getMeasuredHeight();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001302 childState = combineMeasuredStates(childState, child.getMeasuredState());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303
Romain Guy9c3184cc2010-02-25 17:32:54 -08001304 if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
1305 ((LayoutParams) child.getLayoutParams()).viewType)) {
Alan Viverette4771b552014-08-15 18:02:23 -07001306 mRecycler.addScrapView(child, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001307 }
1308 }
1309
1310 if (widthMode == MeasureSpec.UNSPECIFIED) {
1311 widthSize = mListPadding.left + mListPadding.right + childWidth +
1312 getVerticalScrollbarWidth();
Dianne Hackborn189ee182010-12-02 21:48:53 -08001313 } else {
Alan Viverette2ea32922015-06-26 13:31:50 -07001314 widthSize |= (childState & MEASURED_STATE_MASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 }
1316
1317 if (heightMode == MeasureSpec.UNSPECIFIED) {
1318 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
1319 getVerticalFadingEdgeLength() * 2;
1320 }
1321
1322 if (heightMode == MeasureSpec.AT_MOST) {
1323 // TODO: after first layout we should maybe start at the first visible position, not 0
1324 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
1325 }
1326
Alan Viverette2ea32922015-06-26 13:31:50 -07001327 setMeasuredDimension(widthSize, heightSize);
1328
1329 mWidthMeasureSpec = widthMeasureSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 }
1331
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001332 private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001333 LayoutParams p = (LayoutParams) child.getLayoutParams();
1334 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08001335 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project4df24232009-03-05 14:34:35 -08001336 child.setLayoutParams(p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 }
1338 p.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04001339 p.isEnabled = mAdapter.isEnabled(position);
Romain Guy0bf88592010-03-02 13:38:44 -08001340 p.forceAdd = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341
Alan Viverette2ea32922015-06-26 13:31:50 -07001342 final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343 mListPadding.left + mListPadding.right, p.width);
Alan Viverette2ea32922015-06-26 13:31:50 -07001344 final int lpHeight = p.height;
1345 final int childHeightSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 if (lpHeight > 0) {
1347 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
1348 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07001349 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(heightHint, MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 }
1351 child.measure(childWidthSpec, childHeightSpec);
Alan Viverette2ea32922015-06-26 13:31:50 -07001352
1353 // Since this view was measured directly aginst the parent measure
1354 // spec, we must measure it again before reuse.
1355 child.forceLayout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 }
1357
1358 /**
1359 * @return True to recycle the views used to measure this ListView in
1360 * UNSPECIFIED/AT_MOST modes, false otherwise.
1361 * @hide
1362 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001363 @ViewDebug.ExportedProperty(category = "list")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364 protected boolean recycleOnMeasure() {
1365 return true;
1366 }
1367
1368 /**
1369 * Measures the height of the given range of children (inclusive) and
1370 * returns the height with this ListView's padding and divider heights
1371 * included. If maxHeight is provided, the measuring will stop when the
1372 * current height reaches maxHeight.
1373 *
1374 * @param widthMeasureSpec The width measure spec to be given to a child's
1375 * {@link View#measure(int, int)}.
1376 * @param startPosition The position of the first child to be shown.
1377 * @param endPosition The (inclusive) position of the last child to be
1378 * shown. Specify {@link #NO_POSITION} if the last child should be
1379 * the last available child from the adapter.
1380 * @param maxHeight The maximum height that will be returned (if all the
1381 * children don't fit in this value, this value will be
1382 * returned).
1383 * @param disallowPartialChildPosition In general, whether the returned
1384 * height should only contain entire children. This is more
1385 * powerful--it is the first inclusive position at which partial
1386 * children will not be allowed. Example: it looks nice to have
1387 * at least 3 completely visible children, and in portrait this
1388 * will most likely fit; but in landscape there could be times
1389 * when even 2 children can not be completely shown, so a value
1390 * of 2 (remember, inclusive) would be good (assuming
1391 * startPosition is 0).
1392 * @return The height of this ListView with the given children.
1393 */
Mathew Inwood31755f92018-12-20 13:53:36 +00001394 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
Alan Viverette2ea32922015-06-26 13:31:50 -07001396 int maxHeight, int disallowPartialChildPosition) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397 final ListAdapter adapter = mAdapter;
1398 if (adapter == null) {
1399 return mListPadding.top + mListPadding.bottom;
1400 }
1401
1402 // Include the padding of the list
1403 int returnedHeight = mListPadding.top + mListPadding.bottom;
Alan Viverettefea40132016-04-27 16:31:33 -04001404 final int dividerHeight = mDividerHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001405 // The previous height value that was less than maxHeight and contained
1406 // no partial children
1407 int prevHeightWithoutPartialChild = 0;
1408 int i;
1409 View child;
1410
1411 // mItemCount - 1 since endPosition parameter is inclusive
1412 endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
1413 final AbsListView.RecycleBin recycleBin = mRecycler;
1414 final boolean recyle = recycleOnMeasure();
Romain Guy21875052010-01-06 18:48:08 -08001415 final boolean[] isScrap = mIsScrap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416
1417 for (i = startPosition; i <= endPosition; ++i) {
Romain Guy21875052010-01-06 18:48:08 -08001418 child = obtainView(i, isScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001419
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001420 measureScrapChild(child, i, widthMeasureSpec, maxHeight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421
1422 if (i > 0) {
1423 // Count the divider for all but one child
1424 returnedHeight += dividerHeight;
1425 }
1426
1427 // Recycle the view before we possibly return from the method
Romain Guy9c3184cc2010-02-25 17:32:54 -08001428 if (recyle && recycleBin.shouldRecycleViewType(
1429 ((LayoutParams) child.getLayoutParams()).viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001430 recycleBin.addScrapView(child, -1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001431 }
1432
1433 returnedHeight += child.getMeasuredHeight();
1434
1435 if (returnedHeight >= maxHeight) {
1436 // We went over, figure out which height to return. If returnedHeight > maxHeight,
1437 // then the i'th position did not fit completely.
1438 return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
1439 && (i > disallowPartialChildPosition) // We've past the min pos
1440 && (prevHeightWithoutPartialChild > 0) // We have a prev height
1441 && (returnedHeight != maxHeight) // i'th child did not fit completely
1442 ? prevHeightWithoutPartialChild
1443 : maxHeight;
1444 }
1445
1446 if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
1447 prevHeightWithoutPartialChild = returnedHeight;
1448 }
1449 }
1450
1451 // At this point, we went through the range of children, and they each
1452 // completely fit, so return the returnedHeight
1453 return returnedHeight;
1454 }
1455
1456 @Override
1457 int findMotionRow(int y) {
1458 int childCount = getChildCount();
1459 if (childCount > 0) {
Adam Powell84222e02010-03-11 13:42:57 -08001460 if (!mStackFromBottom) {
1461 for (int i = 0; i < childCount; i++) {
1462 View v = getChildAt(i);
1463 if (y <= v.getBottom()) {
1464 return mFirstPosition + i;
1465 }
1466 }
1467 } else {
1468 for (int i = childCount - 1; i >= 0; i--) {
1469 View v = getChildAt(i);
1470 if (y >= v.getTop()) {
1471 return mFirstPosition + i;
1472 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001473 }
1474 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475 }
1476 return INVALID_POSITION;
1477 }
1478
1479 /**
1480 * Put a specific item at a specific location on the screen and then build
1481 * up and down from there.
1482 *
1483 * @param position The reference view to use as the starting point
1484 * @param top Pixel offset from the top of this view to the top of the
1485 * reference view.
1486 *
1487 * @return The selected view, or null if the selected view is outside the
1488 * visible area.
1489 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01001490 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 private View fillSpecific(int position, int top) {
1492 boolean tempIsSelected = position == mSelectedPosition;
1493 View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
1494 // Possibly changed again in fillUp if we add rows above this one.
1495 mFirstPosition = position;
1496
1497 View above;
1498 View below;
1499
1500 final int dividerHeight = mDividerHeight;
1501 if (!mStackFromBottom) {
1502 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1503 // This will correct for the top of the first view not touching the top of the list
1504 adjustViewsUpOrDown();
1505 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1506 int childCount = getChildCount();
1507 if (childCount > 0) {
1508 correctTooHigh(childCount);
1509 }
1510 } else {
1511 below = fillDown(position + 1, temp.getBottom() + dividerHeight);
1512 // This will correct for the bottom of the last view not touching the bottom of the list
1513 adjustViewsUpOrDown();
1514 above = fillUp(position - 1, temp.getTop() - dividerHeight);
1515 int childCount = getChildCount();
1516 if (childCount > 0) {
1517 correctTooLow(childCount);
1518 }
1519 }
1520
1521 if (tempIsSelected) {
1522 return temp;
1523 } else if (above != null) {
1524 return above;
1525 } else {
1526 return below;
1527 }
1528 }
1529
1530 /**
1531 * Check if we have dragged the bottom of the list too high (we have pushed the
1532 * top element off the top of the screen when we did not need to). Correct by sliding
1533 * everything back down.
1534 *
1535 * @param childCount Number of children
1536 */
Mathew Inwood31755f92018-12-20 13:53:36 +00001537 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001538 private void correctTooHigh(int childCount) {
1539 // First see if the last item is visible. If it is not, it is OK for the
1540 // top of the list to be pushed up.
1541 int lastPosition = mFirstPosition + childCount - 1;
1542 if (lastPosition == mItemCount - 1 && childCount > 0) {
1543
1544 // Get the last child ...
1545 final View lastChild = getChildAt(childCount - 1);
1546
1547 // ... and its bottom edge
1548 final int lastBottom = lastChild.getBottom();
1549
1550 // This is bottom of our drawable area
1551 final int end = (mBottom - mTop) - mListPadding.bottom;
1552
1553 // This is how far the bottom edge of the last view is from the bottom of the
1554 // drawable area
1555 int bottomOffset = end - lastBottom;
1556 View firstChild = getChildAt(0);
1557 final int firstTop = firstChild.getTop();
1558
1559 // Make sure we are 1) Too high, and 2) Either there are more rows above the
1560 // first row or the first row is scrolled off the top of the drawable area
1561 if (bottomOffset > 0 && (mFirstPosition > 0 || firstTop < mListPadding.top)) {
1562 if (mFirstPosition == 0) {
1563 // Don't pull the top too far down
1564 bottomOffset = Math.min(bottomOffset, mListPadding.top - firstTop);
1565 }
1566 // Move everything down
1567 offsetChildrenTopAndBottom(bottomOffset);
1568 if (mFirstPosition > 0) {
1569 // Fill the gap that was opened above mFirstPosition with more rows, if
1570 // possible
1571 fillUp(mFirstPosition - 1, firstChild.getTop() - mDividerHeight);
1572 // Close up the remaining gap
1573 adjustViewsUpOrDown();
1574 }
1575
1576 }
1577 }
1578 }
1579
1580 /**
1581 * Check if we have dragged the bottom of the list too low (we have pushed the
1582 * bottom element off the bottom of the screen when we did not need to). Correct by sliding
1583 * everything back up.
1584 *
1585 * @param childCount Number of children
1586 */
Mathew Inwood31755f92018-12-20 13:53:36 +00001587 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001588 private void correctTooLow(int childCount) {
1589 // First see if the first item is visible. If it is not, it is OK for the
1590 // bottom of the list to be pushed down.
1591 if (mFirstPosition == 0 && childCount > 0) {
1592
1593 // Get the first child ...
1594 final View firstChild = getChildAt(0);
1595
1596 // ... and its top edge
1597 final int firstTop = firstChild.getTop();
1598
1599 // This is top of our drawable area
1600 final int start = mListPadding.top;
1601
1602 // This is bottom of our drawable area
1603 final int end = (mBottom - mTop) - mListPadding.bottom;
1604
1605 // This is how far the top edge of the first view is from the top of the
1606 // drawable area
1607 int topOffset = firstTop - start;
1608 View lastChild = getChildAt(childCount - 1);
1609 final int lastBottom = lastChild.getBottom();
1610 int lastPosition = mFirstPosition + childCount - 1;
1611
1612 // Make sure we are 1) Too low, and 2) Either there are more rows below the
1613 // last row or the last row is scrolled off the bottom of the drawable area
Romain Guy6198ae82009-08-31 17:45:55 -07001614 if (topOffset > 0) {
1615 if (lastPosition < mItemCount - 1 || lastBottom > end) {
1616 if (lastPosition == mItemCount - 1) {
1617 // Don't pull the bottom too far up
1618 topOffset = Math.min(topOffset, lastBottom - end);
1619 }
1620 // Move everything up
1621 offsetChildrenTopAndBottom(-topOffset);
1622 if (lastPosition < mItemCount - 1) {
1623 // Fill the gap that was opened below the last position with more rows, if
1624 // possible
1625 fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight);
1626 // Close up the remaining gap
1627 adjustViewsUpOrDown();
1628 }
1629 } else if (lastPosition == mItemCount - 1) {
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001630 adjustViewsUpOrDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001631 }
1632 }
1633 }
1634 }
1635
1636 @Override
1637 protected void layoutChildren() {
1638 final boolean blockLayoutRequests = mBlockLayoutRequests;
Alan Viveretted44696c2013-07-18 10:37:15 -07001639 if (blockLayoutRequests) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001640 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001641 }
1642
Alan Viveretted44696c2013-07-18 10:37:15 -07001643 mBlockLayoutRequests = true;
1644
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001645 try {
1646 super.layoutChildren();
1647
1648 invalidate();
1649
1650 if (mAdapter == null) {
1651 resetList();
1652 invokeOnItemScrollListener();
1653 return;
1654 }
1655
Alan Viveretted44696c2013-07-18 10:37:15 -07001656 final int childrenTop = mListPadding.top;
1657 final int childrenBottom = mBottom - mTop - mListPadding.bottom;
1658 final int childCount = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001659
Romain Guyead0d4d2009-12-08 17:33:53 -08001660 int index = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661 int delta = 0;
1662
1663 View sel;
1664 View oldSel = null;
1665 View oldFirst = null;
1666 View newSel = null;
1667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001668 // Remember stuff we will need down below
1669 switch (mLayoutMode) {
1670 case LAYOUT_SET_SELECTION:
1671 index = mNextSelectedPosition - mFirstPosition;
1672 if (index >= 0 && index < childCount) {
1673 newSel = getChildAt(index);
1674 }
1675 break;
1676 case LAYOUT_FORCE_TOP:
1677 case LAYOUT_FORCE_BOTTOM:
1678 case LAYOUT_SPECIFIC:
1679 case LAYOUT_SYNC:
1680 break;
1681 case LAYOUT_MOVE_SELECTION:
1682 default:
1683 // Remember the previously selected view
1684 index = mSelectedPosition - mFirstPosition;
1685 if (index >= 0 && index < childCount) {
1686 oldSel = getChildAt(index);
1687 }
1688
1689 // Remember the previous first child
1690 oldFirst = getChildAt(0);
1691
1692 if (mNextSelectedPosition >= 0) {
1693 delta = mNextSelectedPosition - mSelectedPosition;
1694 }
1695
1696 // Caution: newSel might be null
1697 newSel = getChildAt(index + delta);
1698 }
1699
1700
1701 boolean dataChanged = mDataChanged;
1702 if (dataChanged) {
1703 handleDataChanged();
1704 }
1705
1706 // Handle the empty set by removing all views that are visible
1707 // and calling it a day
1708 if (mItemCount == 0) {
1709 resetList();
1710 invokeOnItemScrollListener();
1711 return;
Romain Guyb45f1242009-03-24 21:30:00 -07001712 } else if (mItemCount != mAdapter.getCount()) {
1713 throw new IllegalStateException("The content of the adapter has changed but "
1714 + "ListView did not receive a notification. Make sure the content of "
Alan Viverette7d8314d2013-09-10 11:22:53 -07001715 + "your adapter is not modified from a background thread, but only from "
1716 + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
1717 + "when its content changes. [in ListView(" + getId() + ", " + getClass()
Owen Lin3940f2d2009-08-13 15:21:16 +08001718 + ") with Adapter(" + mAdapter.getClass() + ")]");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001719 }
1720
1721 setSelectedPositionInt(mNextSelectedPosition);
1722
Alan Viverette3e141622014-02-18 17:05:13 -08001723 AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
1724 View accessibilityFocusLayoutRestoreView = null;
1725 int accessibilityFocusPosition = INVALID_POSITION;
1726
1727 // Remember which child, if any, had accessibility focus. This must
1728 // occur before recycling any views, since that will clear
1729 // accessibility focus.
1730 final ViewRootImpl viewRootImpl = getViewRootImpl();
1731 if (viewRootImpl != null) {
1732 final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
1733 if (focusHost != null) {
1734 final View focusChild = getAccessibilityFocusedChild(focusHost);
1735 if (focusChild != null) {
1736 if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
Phil Weaverec66fb82017-03-23 12:21:53 -07001737 || (focusChild.hasTransientState() && mAdapterHasStableIds)) {
Alan Viverette3e141622014-02-18 17:05:13 -08001738 // The views won't be changing, so try to maintain
1739 // focus on the current host and virtual view.
1740 accessibilityFocusLayoutRestoreView = focusHost;
1741 accessibilityFocusLayoutRestoreNode = viewRootImpl
1742 .getAccessibilityFocusedVirtualView();
1743 }
1744
1745 // If all else fails, maintain focus at the same
1746 // position.
1747 accessibilityFocusPosition = getPositionForView(focusChild);
1748 }
1749 }
Alan Viveretteb53c5f62013-03-15 15:50:37 -07001750 }
1751
Alan Viverette3e141622014-02-18 17:05:13 -08001752 View focusLayoutRestoreDirectChild = null;
1753 View focusLayoutRestoreView = null;
1754
1755 // Take focus back to us temporarily to avoid the eventual call to
1756 // clear focus when removing the focused child below from messing
1757 // things up when ViewAncestor assigns focus back to someone else.
Alan Viveretted44696c2013-07-18 10:37:15 -07001758 final View focusedChild = getFocusedChild();
1759 if (focusedChild != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001760 // TODO: in some cases focusedChild.getParent() == null
1761
1762 // We can remember the focused view to restore after re-layout
1763 // if the data hasn't changed, or if the focused position is a
1764 // header or footer.
Alan Viverette2b460d02015-07-06 11:01:50 -07001765 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
1766 || focusedChild.hasTransientState() || mAdapterHasStableIds) {
Alan Viverette3e141622014-02-18 17:05:13 -08001767 focusLayoutRestoreDirectChild = focusedChild;
1768 // Remember the specific view that had focus.
1769 focusLayoutRestoreView = findFocus();
1770 if (focusLayoutRestoreView != null) {
1771 // Tell it we are going to mess with it.
Yohei Yukawa24df9312016-03-31 17:15:23 -07001772 focusLayoutRestoreView.dispatchStartTemporaryDetach();
Alan Viverette3e141622014-02-18 17:05:13 -08001773 }
1774 }
1775 requestFocus();
Alan Viveretted44696c2013-07-18 10:37:15 -07001776 }
1777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 // Pull all children into the RecycleBin.
1779 // These views will be reused if possible
1780 final int firstPosition = mFirstPosition;
1781 final RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 if (dataChanged) {
1783 for (int i = 0; i < childCount; i++) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001784 recycleBin.addScrapView(getChildAt(i), firstPosition+i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001785 }
1786 } else {
1787 recycleBin.fillActiveViews(childCount, firstPosition);
1788 }
1789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001790 // Clear out old views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001791 detachAllViewsFromParent();
Adam Powell539ee872012-02-03 19:00:49 -08001792 recycleBin.removeSkippedScrap();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001793
1794 switch (mLayoutMode) {
1795 case LAYOUT_SET_SELECTION:
1796 if (newSel != null) {
1797 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
1798 } else {
1799 sel = fillFromMiddle(childrenTop, childrenBottom);
1800 }
1801 break;
1802 case LAYOUT_SYNC:
1803 sel = fillSpecific(mSyncPosition, mSpecificTop);
1804 break;
1805 case LAYOUT_FORCE_BOTTOM:
1806 sel = fillUp(mItemCount - 1, childrenBottom);
1807 adjustViewsUpOrDown();
1808 break;
1809 case LAYOUT_FORCE_TOP:
1810 mFirstPosition = 0;
1811 sel = fillFromTop(childrenTop);
1812 adjustViewsUpOrDown();
1813 break;
1814 case LAYOUT_SPECIFIC:
Yigit Boyar03633412016-03-24 17:44:28 -07001815 final int selectedPosition = reconcileSelectedPosition();
1816 sel = fillSpecific(selectedPosition, mSpecificTop);
1817 /**
1818 * When ListView is resized, FocusSelector requests an async selection for the
1819 * previously focused item to make sure it is still visible. If the item is not
1820 * selectable, it won't regain focus so instead we call FocusSelector
1821 * to directly request focus on the view after it is visible.
1822 */
1823 if (sel == null && mFocusSelector != null) {
1824 final Runnable focusRunnable = mFocusSelector
1825 .setupFocusIfValid(selectedPosition);
1826 if (focusRunnable != null) {
1827 post(focusRunnable);
1828 }
1829 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830 break;
1831 case LAYOUT_MOVE_SELECTION:
1832 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
1833 break;
1834 default:
1835 if (childCount == 0) {
1836 if (!mStackFromBottom) {
1837 final int position = lookForSelectablePosition(0, true);
1838 setSelectedPositionInt(position);
1839 sel = fillFromTop(childrenTop);
1840 } else {
1841 final int position = lookForSelectablePosition(mItemCount - 1, false);
1842 setSelectedPositionInt(position);
1843 sel = fillUp(mItemCount - 1, childrenBottom);
1844 }
1845 } else {
1846 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
1847 sel = fillSpecific(mSelectedPosition,
1848 oldSel == null ? childrenTop : oldSel.getTop());
1849 } else if (mFirstPosition < mItemCount) {
1850 sel = fillSpecific(mFirstPosition,
1851 oldFirst == null ? childrenTop : oldFirst.getTop());
1852 } else {
1853 sel = fillSpecific(0, childrenTop);
1854 }
1855 }
1856 break;
1857 }
1858
1859 // Flush any cached views that did not get reused above
1860 recycleBin.scrapActiveViews();
1861
Yigit Boyarb742b872016-05-06 16:11:12 -07001862 // remove any header/footer that has been temp detached and not re-attached
1863 removeUnusedFixedViews(mHeaderViewInfos);
1864 removeUnusedFixedViews(mFooterViewInfos);
1865
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001866 if (sel != null) {
Alan Viverette3e141622014-02-18 17:05:13 -08001867 // The current selected item should get focus if items are
1868 // focusable.
1869 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
1870 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
1871 focusLayoutRestoreView != null &&
1872 focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
1873 if (!focusWasTaken) {
1874 // Selected item didn't take focus, but we still want to
1875 // make sure something else outside of the selected view
1876 // has focus.
Romain Guy3616a412009-09-15 13:50:37 -07001877 final View focused = getFocusedChild();
1878 if (focused != null) {
1879 focused.clearFocus();
1880 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07001881 positionSelector(INVALID_POSITION, sel);
Alan Viverette3e141622014-02-18 17:05:13 -08001882 } else {
1883 sel.setSelected(false);
1884 mSelectorRect.setEmpty();
Romain Guy3616a412009-09-15 13:50:37 -07001885 }
1886 } else {
Dianne Hackborn079e2352010-10-18 17:02:43 -07001887 positionSelector(INVALID_POSITION, sel);
Romain Guy3616a412009-09-15 13:50:37 -07001888 }
1889 mSelectedTop = sel.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001890 } else {
Alan Viverettee3c433a2014-06-19 12:48:45 -07001891 final boolean inTouchMode = mTouchMode == TOUCH_MODE_TAP
1892 || mTouchMode == TOUCH_MODE_DONE_WAITING;
1893 if (inTouchMode) {
1894 // If the user's finger is down, select the motion position.
Alan Viveretted44696c2013-07-18 10:37:15 -07001895 final View child = getChildAt(mMotionPosition - mFirstPosition);
Alan Viverettee3c433a2014-06-19 12:48:45 -07001896 if (child != null) {
Alan Viveretted44696c2013-07-18 10:37:15 -07001897 positionSelector(mMotionPosition, child);
1898 }
Alan Viverettee3c433a2014-06-19 12:48:45 -07001899 } else if (mSelectorPosition != INVALID_POSITION) {
1900 // If we had previously positioned the selector somewhere,
1901 // put it back there. It might not match up with the data,
1902 // but it's transitioning out so it's not a big deal.
1903 final View child = getChildAt(mSelectorPosition - mFirstPosition);
1904 if (child != null) {
1905 positionSelector(mSelectorPosition, child);
1906 }
Romain Guy3616a412009-09-15 13:50:37 -07001907 } else {
Alan Viverettee3c433a2014-06-19 12:48:45 -07001908 // Otherwise, clear selection.
Romain Guy3616a412009-09-15 13:50:37 -07001909 mSelectedTop = 0;
1910 mSelectorRect.setEmpty();
1911 }
Alan Viverette3e141622014-02-18 17:05:13 -08001912
1913 // Even if there is not selected position, we may need to
1914 // restore focus (i.e. something focusable in touch mode).
1915 if (hasFocus() && focusLayoutRestoreView != null) {
1916 focusLayoutRestoreView.requestFocus();
1917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001918 }
1919
Alan Viverette3e141622014-02-18 17:05:13 -08001920 // Attempt to restore accessibility focus, if necessary.
Alan Viverette2e6fc8c2014-02-24 11:09:18 -08001921 if (viewRootImpl != null) {
1922 final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
1923 if (newAccessibilityFocusedView == null) {
1924 if (accessibilityFocusLayoutRestoreView != null
1925 && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
1926 final AccessibilityNodeProvider provider =
1927 accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
1928 if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
1929 final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
1930 accessibilityFocusLayoutRestoreNode.getSourceNodeId());
1931 provider.performAction(virtualViewId,
1932 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
1933 } else {
1934 accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
1935 }
1936 } else if (accessibilityFocusPosition != INVALID_POSITION) {
1937 // Bound the position within the visible children.
1938 final int position = MathUtils.constrain(
1939 accessibilityFocusPosition - mFirstPosition, 0,
1940 getChildCount() - 1);
1941 final View restoreView = getChildAt(position);
1942 if (restoreView != null) {
1943 restoreView.requestAccessibilityFocus();
1944 }
Alan Viverette68207512013-08-22 12:33:07 -07001945 }
alanv30ee76c2012-09-07 10:31:16 -07001946 }
1947 }
1948
Alan Viverette3e141622014-02-18 17:05:13 -08001949 // Tell focus view we are done mucking with it, if it is still in
1950 // our view hierarchy.
1951 if (focusLayoutRestoreView != null
1952 && focusLayoutRestoreView.getWindowToken() != null) {
Yohei Yukawa24df9312016-03-31 17:15:23 -07001953 focusLayoutRestoreView.dispatchFinishTemporaryDetach();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001954 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07001955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001956 mLayoutMode = LAYOUT_NORMAL;
1957 mDataChanged = false;
Adam Powell161abf32012-05-23 17:22:49 -07001958 if (mPositionScrollAfterLayout != null) {
1959 post(mPositionScrollAfterLayout);
1960 mPositionScrollAfterLayout = null;
1961 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001962 mNeedSync = false;
1963 setNextSelectedPositionInt(mSelectedPosition);
1964
1965 updateScrollIndicators();
1966
1967 if (mItemCount > 0) {
1968 checkSelectionChanged();
1969 }
1970
1971 invokeOnItemScrollListener();
1972 } finally {
Yigit Boyar03633412016-03-24 17:44:28 -07001973 if (mFocusSelector != null) {
1974 mFocusSelector.onLayoutComplete();
1975 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001976 if (!blockLayoutRequests) {
1977 mBlockLayoutRequests = false;
1978 }
1979 }
1980 }
1981
Yigit Boyarb742b872016-05-06 16:11:12 -07001982 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +01001983 @UnsupportedAppUsage
Yigit Boyarb742b872016-05-06 16:11:12 -07001984 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
1985 final boolean result = super.trackMotionScroll(deltaY, incrementalDeltaY);
1986 removeUnusedFixedViews(mHeaderViewInfos);
1987 removeUnusedFixedViews(mFooterViewInfos);
1988 return result;
1989 }
1990
1991 /**
1992 * Header and Footer views are not scrapped / recycled like other views but they are still
1993 * detached from the ViewGroup. After a layout operation, call this method to remove such views.
1994 *
1995 * @param infoList The info list to be traversed
1996 */
1997 private void removeUnusedFixedViews(@Nullable List<FixedViewInfo> infoList) {
1998 if (infoList == null) {
1999 return;
2000 }
2001 for (int i = infoList.size() - 1; i >= 0; i--) {
2002 final FixedViewInfo fixedViewInfo = infoList.get(i);
2003 final View view = fixedViewInfo.view;
2004 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
2005 if (view.getParent() == null && lp != null && lp.recycledHeaderFooter) {
2006 removeDetachedView(view, false);
2007 lp.recycledHeaderFooter = false;
2008 }
2009
2010 }
2011 }
2012
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002013 /**
Alan Viverette3e141622014-02-18 17:05:13 -08002014 * @param child a direct child of this list.
2015 * @return Whether child is a header or footer view.
2016 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01002017 @UnsupportedAppUsage
Alan Viverette3e141622014-02-18 17:05:13 -08002018 private boolean isDirectChildHeaderOrFooter(View child) {
2019 final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
2020 final int numHeaders = headers.size();
2021 for (int i = 0; i < numHeaders; i++) {
2022 if (child == headers.get(i).view) {
2023 return true;
2024 }
2025 }
2026
2027 final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
2028 final int numFooters = footers.size();
2029 for (int i = 0; i < numFooters; i++) {
2030 if (child == footers.get(i).view) {
2031 return true;
2032 }
2033 }
2034
2035 return false;
2036 }
2037
2038 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002039 * Obtains the view and adds it to our list of children. The view can be
2040 * made fresh, converted from an unused view, or used as is if it was in
2041 * the recycle bin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002042 *
Alan Viverette26489e12016-07-07 16:39:27 -04002043 * @param position logical position in the list
2044 * @param y top or bottom edge of the view to add
2045 * @param flow {@code true} to align top edge to y, {@code false} to align
2046 * bottom edge to y
2047 * @param childrenLeft left edge where children should be positioned
2048 * @param selected {@code true} if the position is selected, {@code false}
2049 * otherwise
2050 * @return the view that was added
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002051 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01002052 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002053 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
2054 boolean selected) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002055 if (!mDataChanged) {
Alan Viverette26489e12016-07-07 16:39:27 -04002056 // Try to use an existing view for this position.
2057 final View activeView = mRecycler.getActiveView(position);
2058 if (activeView != null) {
2059 // Found it. We're reusing an existing child, so it just needs
2060 // to be positioned like a scrap view.
2061 setupChild(activeView, position, y, flow, childrenLeft, selected, true);
2062 return activeView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002063 }
2064 }
2065
Alan Viverette26489e12016-07-07 16:39:27 -04002066 // Make a new view for this position, or convert an unused view if
2067 // possible.
2068 final View child = obtainView(position, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002069
Alan Viverette26489e12016-07-07 16:39:27 -04002070 // This needs to be positioned and measured.
Romain Guy21875052010-01-06 18:48:08 -08002071 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002072
2073 return child;
2074 }
2075
2076 /**
Alan Viverette26489e12016-07-07 16:39:27 -04002077 * Adds a view as a child and make sure it is measured (if necessary) and
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002078 * positioned properly.
2079 *
Alan Viverette26489e12016-07-07 16:39:27 -04002080 * @param child the view to add
2081 * @param position the position of this child
2082 * @param y the y position relative to which this view will be positioned
2083 * @param flowDown {@code true} to align top edge to y, {@code false} to
2084 * align bottom edge to y
2085 * @param childrenLeft left edge where children should be positioned
2086 * @param selected {@code true} if the position is selected, {@code false}
2087 * otherwise
2088 * @param isAttachedToWindow {@code true} if the view is already attached
2089 * to the window, e.g. whether it was reused, or
2090 * {@code false} otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002091 */
2092 private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
Alan Viverette26489e12016-07-07 16:39:27 -04002093 boolean selected, boolean isAttachedToWindow) {
Romain Guy5fade8c2013-07-10 16:36:18 -07002094 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
2095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002096 final boolean isSelected = selected && shouldShowSelector();
2097 final boolean updateChildSelected = isSelected != child.isSelected();
Romain Guy3616a412009-09-15 13:50:37 -07002098 final int mode = mTouchMode;
Alan Viverette26489e12016-07-07 16:39:27 -04002099 final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL
2100 && mMotionPosition == position;
Romain Guy3616a412009-09-15 13:50:37 -07002101 final boolean updateChildPressed = isPressed != child.isPressed();
Alan Viverette26489e12016-07-07 16:39:27 -04002102 final boolean needToMeasure = !isAttachedToWindow || updateChildSelected
2103 || child.isLayoutRequested();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002104
Alan Viverette26489e12016-07-07 16:39:27 -04002105 // Respect layout params that are already in the view. Otherwise make
2106 // some up...
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002107 AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
2108 if (p == null) {
Adam Powellaebd28f2012-02-22 10:31:16 -08002109 p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002110 }
2111 p.viewType = mAdapter.getItemViewType(position);
Alan Viverette92539d52015-09-14 10:49:25 -04002112 p.isEnabled = mAdapter.isEnabled(position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002113
Alan Viverette26489e12016-07-07 16:39:27 -04002114 // Set up view state before attaching the view, since we may need to
2115 // rely on the jumpDrawablesToCurrentState() call that occurs as part
2116 // of view attachment.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002117 if (updateChildSelected) {
2118 child.setSelected(isSelected);
2119 }
2120
Romain Guy3616a412009-09-15 13:50:37 -07002121 if (updateChildPressed) {
2122 child.setPressed(isPressed);
2123 }
2124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002125 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
2126 if (child instanceof Checkable) {
2127 ((Checkable) child).setChecked(mCheckStates.get(position));
Dianne Hackbornd0fa3712010-09-14 18:57:14 -07002128 } else if (getContext().getApplicationInfo().targetSdkVersion
2129 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
2130 child.setActivated(mCheckStates.get(position));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002131 }
2132 }
2133
Alan Viverette26489e12016-07-07 16:39:27 -04002134 if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
2135 && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
2136 attachViewToParent(child, flowDown ? -1 : 0, p);
2137
2138 // If the view was previously attached for a different position,
2139 // then manually jump the drawables.
2140 if (isAttachedToWindow
2141 && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
2142 != position) {
2143 child.jumpDrawablesToCurrentState();
2144 }
2145 } else {
2146 p.forceAdd = false;
2147 if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
2148 p.recycledHeaderFooter = true;
2149 }
2150 addViewInLayout(child, flowDown ? -1 : 0, p, true);
Yigit Boyar4d827aa2016-07-19 15:32:09 -07002151 // add view in layout will reset the RTL properties. We have to re-resolve them
2152 child.resolveRtlPropertiesIfNeeded();
Alan Viverette26489e12016-07-07 16:39:27 -04002153 }
2154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002155 if (needToMeasure) {
Alan Viverette2ea32922015-06-26 13:31:50 -07002156 final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002157 mListPadding.left + mListPadding.right, p.width);
Alan Viverette2ea32922015-06-26 13:31:50 -07002158 final int lpHeight = p.height;
2159 final int childHeightSpec;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002160 if (lpHeight > 0) {
2161 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2162 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07002163 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07002164 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002165 }
2166 child.measure(childWidthSpec, childHeightSpec);
2167 } else {
2168 cleanupLayoutState(child);
2169 }
2170
2171 final int w = child.getMeasuredWidth();
2172 final int h = child.getMeasuredHeight();
2173 final int childTop = flowDown ? y : y - h;
2174
2175 if (needToMeasure) {
2176 final int childRight = childrenLeft + w;
2177 final int childBottom = childTop + h;
2178 child.layout(childrenLeft, childTop, childRight, childBottom);
2179 } else {
2180 child.offsetLeftAndRight(childrenLeft - child.getLeft());
2181 child.offsetTopAndBottom(childTop - child.getTop());
2182 }
2183
2184 if (mCachingStarted && !child.isDrawingCacheEnabled()) {
2185 child.setDrawingCacheEnabled(true);
2186 }
Dianne Hackborn079e2352010-10-18 17:02:43 -07002187
Romain Guy5fade8c2013-07-10 16:36:18 -07002188 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002189 }
2190
2191 @Override
2192 protected boolean canAnimate() {
2193 return super.canAnimate() && mItemCount > 0;
2194 }
2195
2196 /**
2197 * Sets the currently selected item. If in touch mode, the item will not be selected
2198 * but it will still be positioned appropriately. If the specified selection position
2199 * is less than 0, then the item at position 0 will be selected.
2200 *
2201 * @param position Index (starting at 0) of the data item to be selected.
2202 */
2203 @Override
2204 public void setSelection(int position) {
2205 setSelectionFromTop(position, 0);
2206 }
2207
2208 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002209 * Makes the item at the supplied position selected.
Aurimas Liutikas99441c52016-10-11 16:48:32 -07002210 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002211 * @param position the position of the item to select
2212 */
2213 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +01002214 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002215 void setSelectionInt(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002216 setNextSelectedPositionInt(position);
Mike Cleronf116bf82009-09-27 19:14:12 -07002217 boolean awakeScrollbars = false;
2218
2219 final int selectedPosition = mSelectedPosition;
2220
2221 if (selectedPosition >= 0) {
2222 if (position == selectedPosition - 1) {
2223 awakeScrollbars = true;
2224 } else if (position == selectedPosition + 1) {
2225 awakeScrollbars = true;
2226 }
2227 }
2228
Adam Powell1fa179ef2012-04-12 15:01:40 -07002229 if (mPositionScroller != null) {
2230 mPositionScroller.stop();
2231 }
2232
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002233 layoutChildren();
Mike Cleronf116bf82009-09-27 19:14:12 -07002234
2235 if (awakeScrollbars) {
2236 awakenScrollBars();
2237 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002238 }
2239
2240 /**
2241 * Find a position that can be selected (i.e., is not a separator).
2242 *
2243 * @param position The starting position to look at.
2244 * @param lookDown Whether to look down for other positions.
2245 * @return The next selectable position starting at position and then searching either up or
2246 * down. Returns {@link #INVALID_POSITION} if nothing can be found.
2247 */
2248 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +01002249 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002250 int lookForSelectablePosition(int position, boolean lookDown) {
2251 final ListAdapter adapter = mAdapter;
2252 if (adapter == null || isInTouchMode()) {
2253 return INVALID_POSITION;
2254 }
2255
2256 final int count = adapter.getCount();
2257 if (!mAreAllItemsSelectable) {
2258 if (lookDown) {
2259 position = Math.max(0, position);
2260 while (position < count && !adapter.isEnabled(position)) {
2261 position++;
2262 }
2263 } else {
2264 position = Math.min(position, count - 1);
2265 while (position >= 0 && !adapter.isEnabled(position)) {
2266 position--;
2267 }
2268 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002269 }
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002270
2271 if (position < 0 || position >= count) {
2272 return INVALID_POSITION;
2273 }
2274
2275 return position;
2276 }
2277
2278 /**
2279 * Find a position that can be selected (i.e., is not a separator). If there
2280 * are no selectable positions in the specified direction from the starting
2281 * position, searches in the opposite direction from the starting position
2282 * to the current position.
2283 *
2284 * @param current the current position
2285 * @param position the starting position
2286 * @param lookDown whether to look down for other positions
2287 * @return the next selectable position, or {@link #INVALID_POSITION} if
2288 * nothing can be found
2289 */
2290 int lookForSelectablePositionAfter(int current, int position, boolean lookDown) {
2291 final ListAdapter adapter = mAdapter;
2292 if (adapter == null || isInTouchMode()) {
2293 return INVALID_POSITION;
2294 }
2295
2296 // First check after the starting position in the specified direction.
2297 final int after = lookForSelectablePosition(position, lookDown);
2298 if (after != INVALID_POSITION) {
2299 return after;
2300 }
2301
2302 // Then check between the starting position and the current position.
2303 final int count = adapter.getCount();
2304 current = MathUtils.constrain(current, -1, count - 1);
2305 if (lookDown) {
2306 position = Math.min(position - 1, count - 1);
2307 while ((position > current) && !adapter.isEnabled(position)) {
2308 position--;
2309 }
2310 if (position <= current) {
2311 return INVALID_POSITION;
2312 }
2313 } else {
2314 position = Math.max(0, position + 1);
2315 while ((position < current) && !adapter.isEnabled(position)) {
2316 position++;
2317 }
2318 if (position >= current) {
2319 return INVALID_POSITION;
2320 }
2321 }
2322
2323 return position;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002324 }
2325
2326 /**
2327 * setSelectionAfterHeaderView set the selection to be the first list item
2328 * after the header views.
2329 */
2330 public void setSelectionAfterHeaderView() {
Michael Kwan744be162016-07-22 18:37:31 -07002331 final int count = getHeaderViewsCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002332 if (count > 0) {
2333 mNextSelectedPosition = 0;
2334 return;
2335 }
2336
2337 if (mAdapter != null) {
2338 setSelection(count);
2339 } else {
2340 mNextSelectedPosition = count;
2341 mLayoutMode = LAYOUT_SET_SELECTION;
2342 }
2343
2344 }
2345
2346 @Override
2347 public boolean dispatchKeyEvent(KeyEvent event) {
2348 // Dispatch in the normal way
2349 boolean handled = super.dispatchKeyEvent(event);
2350 if (!handled) {
2351 // If we didn't handle it...
2352 View focused = getFocusedChild();
2353 if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
2354 // ... and our focused child didn't handle it
2355 // ... give it to ourselves so we can scroll if necessary
2356 handled = onKeyDown(event.getKeyCode(), event);
2357 }
2358 }
2359 return handled;
2360 }
2361
2362 @Override
2363 public boolean onKeyDown(int keyCode, KeyEvent event) {
2364 return commonKey(keyCode, 1, event);
2365 }
2366
2367 @Override
2368 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
2369 return commonKey(keyCode, repeatCount, event);
2370 }
2371
2372 @Override
2373 public boolean onKeyUp(int keyCode, KeyEvent event) {
2374 return commonKey(keyCode, 1, event);
2375 }
2376
2377 private boolean commonKey(int keyCode, int count, KeyEvent event) {
Adam Powell31986b52013-09-24 14:53:30 -07002378 if (mAdapter == null || !isAttachedToWindow()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002379 return false;
2380 }
2381
2382 if (mDataChanged) {
2383 layoutChildren();
2384 }
2385
2386 boolean handled = false;
2387 int action = event.getAction();
Michael Wrightaa1a94d2015-11-26 16:04:54 +00002388 if (KeyEvent.isConfirmKey(keyCode)
George Mount26268f92015-12-18 13:00:46 -08002389 && event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {
Michael Wrightaa1a94d2015-11-26 16:04:54 +00002390 handled = resurrectSelectionIfNeeded();
2391 if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {
2392 keyPressed();
2393 handled = true;
2394 }
2395 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002396
Michael Wrightaa1a94d2015-11-26 16:04:54 +00002397
2398 if (!handled && action != KeyEvent.ACTION_UP) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002399 switch (keyCode) {
2400 case KeyEvent.KEYCODE_DPAD_UP:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002401 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002402 handled = resurrectSelectionIfNeeded();
2403 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002404 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002405 if (arrowScroll(FOCUS_UP)) {
2406 handled = true;
2407 } else {
2408 break;
2409 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002410 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002411 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002412 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002413 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002414 }
2415 break;
2416
2417 case KeyEvent.KEYCODE_DPAD_DOWN:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002418 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002419 handled = resurrectSelectionIfNeeded();
2420 if (!handled) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08002421 while (count-- > 0) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002422 if (arrowScroll(FOCUS_DOWN)) {
2423 handled = true;
2424 } else {
2425 break;
2426 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002428 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08002429 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002430 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002431 }
2432 break;
2433
2434 case KeyEvent.KEYCODE_DPAD_LEFT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002435 if (event.hasNoModifiers()) {
2436 handled = handleHorizontalFocusWithinListItem(View.FOCUS_LEFT);
2437 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002438 break;
Jeff Brown4e6319b2010-12-13 10:36:51 -08002439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002440 case KeyEvent.KEYCODE_DPAD_RIGHT:
Jeff Brown4e6319b2010-12-13 10:36:51 -08002441 if (event.hasNoModifiers()) {
2442 handled = handleHorizontalFocusWithinListItem(View.FOCUS_RIGHT);
2443 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002444 break;
2445
Jeff Brown4e6319b2010-12-13 10:36:51 -08002446 case KeyEvent.KEYCODE_PAGE_UP:
2447 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002448 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002449 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002450 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002451 }
2452 break;
2453
2454 case KeyEvent.KEYCODE_PAGE_DOWN:
2455 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002456 handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002457 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002458 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002459 }
2460 break;
2461
2462 case KeyEvent.KEYCODE_MOVE_HOME:
2463 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002464 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002465 }
2466 break;
2467
2468 case KeyEvent.KEYCODE_MOVE_END:
2469 if (event.hasNoModifiers()) {
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002470 handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002471 }
2472 break;
2473
2474 case KeyEvent.KEYCODE_TAB:
Alan Viverette760a2d82015-08-17 14:31:42 -04002475 // This creates an asymmetry in TAB navigation order. At some
2476 // point in the future we may decide that it's preferable to
2477 // force the list selection to the top or bottom when receiving
2478 // TAB focus from another widget, but for now this is adequate.
2479 if (event.hasNoModifiers()) {
2480 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);
2481 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2482 handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);
Jeff Brown4e6319b2010-12-13 10:36:51 -08002483 }
2484 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002485 }
2486 }
2487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002488 if (handled) {
2489 return true;
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002490 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002491
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002492 if (sendToTextFilter(keyCode, count, event)) {
2493 return true;
2494 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002495
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002496 switch (action) {
2497 case KeyEvent.ACTION_DOWN:
2498 return super.onKeyDown(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002499
Jeff Brown8d6d3b82011-01-26 19:31:18 -08002500 case KeyEvent.ACTION_UP:
2501 return super.onKeyUp(keyCode, event);
2502
2503 case KeyEvent.ACTION_MULTIPLE:
2504 return super.onKeyMultiple(keyCode, count, event);
2505
2506 default: // shouldn't happen
2507 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002508 }
2509 }
2510
2511 /**
2512 * Scrolls up or down by the number of items currently present on screen.
2513 *
2514 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2515 * @return whether selection was moved
2516 */
2517 boolean pageScroll(int direction) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002518 final int nextPage;
2519 final boolean down;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002520
2521 if (direction == FOCUS_UP) {
2522 nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002523 down = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002524 } else if (direction == FOCUS_DOWN) {
2525 nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
2526 down = true;
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002527 } else {
2528 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002529 }
2530
2531 if (nextPage >= 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002532 final int position = lookForSelectablePositionAfter(mSelectedPosition, nextPage, down);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002533 if (position >= 0) {
2534 mLayoutMode = LAYOUT_SPECIFIC;
2535 mSpecificTop = mPaddingTop + getVerticalFadingEdgeLength();
2536
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002537 if (down && (position > (mItemCount - getChildCount()))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002538 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2539 }
2540
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002541 if (!down && (position < getChildCount())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002542 mLayoutMode = LAYOUT_FORCE_TOP;
2543 }
2544
2545 setSelectionInt(position);
2546 invokeOnItemScrollListener();
Mike Cleronf116bf82009-09-27 19:14:12 -07002547 if (!awakenScrollBars()) {
2548 invalidate();
2549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002550
2551 return true;
2552 }
2553 }
2554
2555 return false;
2556 }
2557
2558 /**
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002559 * Go to the last or first item if possible (not worrying about panning
2560 * across or navigating within the internal focus of the currently selected
2561 * item.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002562 *
2563 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002564 * @return whether selection was moved
2565 */
2566 boolean fullScroll(int direction) {
2567 boolean moved = false;
2568 if (direction == FOCUS_UP) {
2569 if (mSelectedPosition != 0) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002570 final int position = lookForSelectablePositionAfter(mSelectedPosition, 0, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002571 if (position >= 0) {
2572 mLayoutMode = LAYOUT_FORCE_TOP;
2573 setSelectionInt(position);
2574 invokeOnItemScrollListener();
2575 }
2576 moved = true;
2577 }
2578 } else if (direction == FOCUS_DOWN) {
Alan Viveretteaf9c5ea2013-06-13 13:27:59 -07002579 final int lastItem = (mItemCount - 1);
2580 if (mSelectedPosition < lastItem) {
2581 final int position = lookForSelectablePositionAfter(
2582 mSelectedPosition, lastItem, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002583 if (position >= 0) {
2584 mLayoutMode = LAYOUT_FORCE_BOTTOM;
2585 setSelectionInt(position);
2586 invokeOnItemScrollListener();
2587 }
2588 moved = true;
2589 }
2590 }
2591
Mike Cleronf116bf82009-09-27 19:14:12 -07002592 if (moved && !awakenScrollBars()) {
2593 awakenScrollBars();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002594 invalidate();
2595 }
2596
2597 return moved;
2598 }
2599
2600 /**
2601 * To avoid horizontal focus searches changing the selected item, we
2602 * manually focus search within the selected item (as applicable), and
2603 * prevent focus from jumping to something within another item.
2604 * @param direction one of {View.FOCUS_LEFT, View.FOCUS_RIGHT}
2605 * @return Whether this consumes the key event.
2606 */
2607 private boolean handleHorizontalFocusWithinListItem(int direction) {
2608 if (direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
Romain Guy304eefa2009-03-24 20:01:49 -07002609 throw new IllegalArgumentException("direction must be one of"
2610 + " {View.FOCUS_LEFT, View.FOCUS_RIGHT}");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002611 }
2612
2613 final int numChildren = getChildCount();
2614 if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
2615 final View selectedView = getSelectedView();
Romain Guy304eefa2009-03-24 20:01:49 -07002616 if (selectedView != null && selectedView.hasFocus() &&
2617 selectedView instanceof ViewGroup) {
2618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002619 final View currentFocus = selectedView.findFocus();
2620 final View nextFocus = FocusFinder.getInstance().findNextFocus(
Romain Guy304eefa2009-03-24 20:01:49 -07002621 (ViewGroup) selectedView, currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002622 if (nextFocus != null) {
2623 // do the math to get interesting rect in next focus' coordinates
Kenji Sugimoto827bb442014-04-10 17:46:23 +09002624 Rect focusedRect = mTempRect;
2625 if (currentFocus != null) {
2626 currentFocus.getFocusedRect(focusedRect);
2627 offsetDescendantRectToMyCoords(currentFocus, focusedRect);
2628 offsetRectIntoDescendantCoords(nextFocus, focusedRect);
2629 } else {
2630 focusedRect = null;
2631 }
2632 if (nextFocus.requestFocus(direction, focusedRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002633 return true;
2634 }
2635 }
2636 // we are blocking the key from being handled (by returning true)
2637 // if the global result is going to be some other view within this
2638 // list. this is to acheive the overall goal of having
2639 // horizontal d-pad navigation remain in the current item.
Romain Guy304eefa2009-03-24 20:01:49 -07002640 final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
2641 (ViewGroup) getRootView(), currentFocus, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002642 if (globalNextFocus != null) {
2643 return isViewAncestorOf(globalNextFocus, this);
2644 }
2645 }
2646 }
2647 return false;
2648 }
2649
2650 /**
2651 * Scrolls to the next or previous item if possible.
2652 *
2653 * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN}
2654 *
2655 * @return whether selection was moved
2656 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01002657 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002658 boolean arrowScroll(int direction) {
2659 try {
2660 mInLayout = true;
2661 final boolean handled = arrowScrollImpl(direction);
2662 if (handled) {
2663 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2664 }
2665 return handled;
2666 } finally {
2667 mInLayout = false;
2668 }
2669 }
2670
2671 /**
Adam Powell2a939112013-03-21 17:08:38 -07002672 * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002673 * to move to. This return a position in the direction given if the selected item
2674 * is fully visible.
Adam Powell2a939112013-03-21 17:08:38 -07002675 *
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002676 * @param selectedView Current selected view to move from
Adam Powell2a939112013-03-21 17:08:38 -07002677 * @param selectedPos Current selected position to move from
2678 * @param direction Direction to move in
2679 * @return Desired selected position after moving in the given direction
2680 */
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002681 private final int nextSelectedPositionForDirection(
2682 View selectedView, int selectedPos, int direction) {
Adam Powell2a939112013-03-21 17:08:38 -07002683 int nextSelected;
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002684
Adam Powell2a939112013-03-21 17:08:38 -07002685 if (direction == View.FOCUS_DOWN) {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002686 final int listBottom = getHeight() - mListPadding.bottom;
2687 if (selectedView != null && selectedView.getBottom() <= listBottom) {
2688 nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
2689 selectedPos + 1 :
2690 mFirstPosition;
2691 } else {
2692 return INVALID_POSITION;
2693 }
Adam Powell2a939112013-03-21 17:08:38 -07002694 } else {
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002695 final int listTop = mListPadding.top;
2696 if (selectedView != null && selectedView.getTop() >= listTop) {
2697 final int lastPos = mFirstPosition + getChildCount() - 1;
2698 nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
2699 selectedPos - 1 :
2700 lastPos;
2701 } else {
2702 return INVALID_POSITION;
2703 }
Adam Powell2a939112013-03-21 17:08:38 -07002704 }
2705
2706 if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
2707 return INVALID_POSITION;
2708 }
2709 return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
2710 }
2711
2712 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002713 * Handle an arrow scroll going up or down. Take into account whether items are selectable,
2714 * whether there are focusable items etc.
2715 *
2716 * @param direction Either {@link android.view.View#FOCUS_UP} or {@link android.view.View#FOCUS_DOWN}.
2717 * @return Whether any scrolling, selection or focus change occured.
2718 */
2719 private boolean arrowScrollImpl(int direction) {
2720 if (getChildCount() <= 0) {
2721 return false;
2722 }
2723
2724 View selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002725 int selectedPos = mSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002726
Jaewan Kim49fc4dc2013-05-21 03:02:30 +09002727 int nextSelectedPosition = nextSelectedPositionForDirection(selectedView, selectedPos, direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728 int amountToScroll = amountToScroll(direction, nextSelectedPosition);
2729
2730 // if we are moving focus, we may OVERRIDE the default behavior
2731 final ArrowScrollFocusResult focusResult = mItemsCanFocus ? arrowScrollFocused(direction) : null;
2732 if (focusResult != null) {
2733 nextSelectedPosition = focusResult.getSelectedPosition();
2734 amountToScroll = focusResult.getAmountToScroll();
2735 }
2736
2737 boolean needToRedraw = focusResult != null;
2738 if (nextSelectedPosition != INVALID_POSITION) {
2739 handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
2740 setSelectedPositionInt(nextSelectedPosition);
2741 setNextSelectedPositionInt(nextSelectedPosition);
2742 selectedView = getSelectedView();
Dianne Hackborn079e2352010-10-18 17:02:43 -07002743 selectedPos = nextSelectedPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002744 if (mItemsCanFocus && focusResult == null) {
2745 // there was no new view found to take focus, make sure we
2746 // don't leave focus with the old selection
2747 final View focused = getFocusedChild();
2748 if (focused != null) {
2749 focused.clearFocus();
2750 }
2751 }
2752 needToRedraw = true;
2753 checkSelectionChanged();
2754 }
2755
2756 if (amountToScroll > 0) {
2757 scrollListItemsBy((direction == View.FOCUS_UP) ? amountToScroll : -amountToScroll);
2758 needToRedraw = true;
2759 }
2760
2761 // if we didn't find a new focusable, make sure any existing focused
2762 // item that was panned off screen gives up focus.
2763 if (mItemsCanFocus && (focusResult == null)
2764 && selectedView != null && selectedView.hasFocus()) {
2765 final View focused = selectedView.findFocus();
Kenji Sugimoto827bb442014-04-10 17:46:23 +09002766 if (focused != null) {
2767 if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
2768 focused.clearFocus();
2769 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002770 }
2771 }
2772
2773 // if the current selection is panned off, we need to remove the selection
2774 if (nextSelectedPosition == INVALID_POSITION && selectedView != null
2775 && !isViewAncestorOf(selectedView, this)) {
2776 selectedView = null;
2777 hideSelector();
2778
2779 // but we don't want to set the ressurect position (that would make subsequent
2780 // unhandled key events bring back the item we just scrolled off!)
2781 mResurrectToPosition = INVALID_POSITION;
2782 }
2783
2784 if (needToRedraw) {
2785 if (selectedView != null) {
Alan Viverettede399392014-05-01 17:20:55 -07002786 positionSelectorLikeFocus(selectedPos, selectedView);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002787 mSelectedTop = selectedView.getTop();
2788 }
Mike Cleronf116bf82009-09-27 19:14:12 -07002789 if (!awakenScrollBars()) {
2790 invalidate();
2791 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002792 invokeOnItemScrollListener();
2793 return true;
2794 }
2795
2796 return false;
2797 }
2798
2799 /**
2800 * When selection changes, it is possible that the previously selected or the
2801 * next selected item will change its size. If so, we need to offset some folks,
2802 * and re-layout the items as appropriate.
2803 *
2804 * @param selectedView The currently selected view (before changing selection).
2805 * should be <code>null</code> if there was no previous selection.
2806 * @param direction Either {@link android.view.View#FOCUS_UP} or
2807 * {@link android.view.View#FOCUS_DOWN}.
2808 * @param newSelectedPosition The position of the next selection.
2809 * @param newFocusAssigned whether new focus was assigned. This matters because
2810 * when something has focus, we don't want to show selection (ugh).
2811 */
2812 private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
2813 boolean newFocusAssigned) {
2814 if (newSelectedPosition == INVALID_POSITION) {
2815 throw new IllegalArgumentException("newSelectedPosition needs to be valid");
2816 }
2817
2818 // whether or not we are moving down or up, we want to preserve the
2819 // top of whatever view is on top:
2820 // - moving down: the view that had selection
2821 // - moving up: the view that is getting selection
2822 View topView;
2823 View bottomView;
2824 int topViewIndex, bottomViewIndex;
2825 boolean topSelected = false;
2826 final int selectedIndex = mSelectedPosition - mFirstPosition;
2827 final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
2828 if (direction == View.FOCUS_UP) {
2829 topViewIndex = nextSelectedIndex;
2830 bottomViewIndex = selectedIndex;
2831 topView = getChildAt(topViewIndex);
2832 bottomView = selectedView;
2833 topSelected = true;
2834 } else {
2835 topViewIndex = selectedIndex;
2836 bottomViewIndex = nextSelectedIndex;
2837 topView = selectedView;
2838 bottomView = getChildAt(bottomViewIndex);
2839 }
2840
2841 final int numChildren = getChildCount();
2842
2843 // start with top view: is it changing size?
2844 if (topView != null) {
2845 topView.setSelected(!newFocusAssigned && topSelected);
2846 measureAndAdjustDown(topView, topViewIndex, numChildren);
2847 }
2848
2849 // is the bottom view changing size?
2850 if (bottomView != null) {
2851 bottomView.setSelected(!newFocusAssigned && !topSelected);
2852 measureAndAdjustDown(bottomView, bottomViewIndex, numChildren);
2853 }
2854 }
2855
2856 /**
2857 * Re-measure a child, and if its height changes, lay it out preserving its
2858 * top, and adjust the children below it appropriately.
2859 * @param child The child
2860 * @param childIndex The view group index of the child.
2861 * @param numChildren The number of children in the view group.
2862 */
2863 private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
2864 int oldHeight = child.getHeight();
2865 measureItem(child);
2866 if (child.getMeasuredHeight() != oldHeight) {
2867 // lay out the view, preserving its top
2868 relayoutMeasuredItem(child);
2869
2870 // adjust views below appropriately
2871 final int heightDelta = child.getMeasuredHeight() - oldHeight;
2872 for (int i = childIndex + 1; i < numChildren; i++) {
2873 getChildAt(i).offsetTopAndBottom(heightDelta);
2874 }
2875 }
2876 }
2877
2878 /**
2879 * Measure a particular list child.
2880 * TODO: unify with setUpChild.
2881 * @param child The child.
2882 */
2883 private void measureItem(View child) {
2884 ViewGroup.LayoutParams p = child.getLayoutParams();
2885 if (p == null) {
2886 p = new ViewGroup.LayoutParams(
Romain Guy980a9382010-01-08 15:06:28 -08002887 ViewGroup.LayoutParams.MATCH_PARENT,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002888 ViewGroup.LayoutParams.WRAP_CONTENT);
2889 }
2890
2891 int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
2892 mListPadding.left + mListPadding.right, p.width);
2893 int lpHeight = p.height;
2894 int childHeightSpec;
2895 if (lpHeight > 0) {
2896 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
2897 } else {
Adam Powelld5dbf4b2015-06-11 13:19:24 -07002898 childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07002899 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002900 }
2901 child.measure(childWidthSpec, childHeightSpec);
2902 }
2903
2904 /**
2905 * Layout a child that has been measured, preserving its top position.
2906 * TODO: unify with setUpChild.
2907 * @param child The child.
2908 */
2909 private void relayoutMeasuredItem(View child) {
2910 final int w = child.getMeasuredWidth();
2911 final int h = child.getMeasuredHeight();
2912 final int childLeft = mListPadding.left;
2913 final int childRight = childLeft + w;
2914 final int childTop = child.getTop();
2915 final int childBottom = childTop + h;
2916 child.layout(childLeft, childTop, childRight, childBottom);
2917 }
2918
2919 /**
2920 * @return The amount to preview next items when arrow srolling.
2921 */
2922 private int getArrowScrollPreviewLength() {
2923 return Math.max(MIN_SCROLL_PREVIEW_PIXELS, getVerticalFadingEdgeLength());
2924 }
2925
2926 /**
2927 * Determine how much we need to scroll in order to get the next selected view
2928 * visible, with a fading edge showing below as applicable. The amount is
2929 * capped at {@link #getMaxScrollAmount()} .
2930 *
2931 * @param direction either {@link android.view.View#FOCUS_UP} or
2932 * {@link android.view.View#FOCUS_DOWN}.
2933 * @param nextSelectedPosition The position of the next selection, or
2934 * {@link #INVALID_POSITION} if there is no next selectable position
2935 * @return The amount to scroll. Note: this is always positive! Direction
2936 * needs to be taken into account when actually scrolling.
2937 */
2938 private int amountToScroll(int direction, int nextSelectedPosition) {
2939 final int listBottom = getHeight() - mListPadding.bottom;
2940 final int listTop = mListPadding.top;
2941
Justin Ho1ad11b92013-02-25 16:09:20 -08002942 int numChildren = getChildCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002943
2944 if (direction == View.FOCUS_DOWN) {
2945 int indexToMakeVisible = numChildren - 1;
2946 if (nextSelectedPosition != INVALID_POSITION) {
2947 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2948 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002949 while (numChildren <= indexToMakeVisible) {
2950 // Child to view is not attached yet.
2951 addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
2952 numChildren++;
2953 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002954 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2955 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2956
2957 int goalBottom = listBottom;
2958 if (positionToMakeVisible < mItemCount - 1) {
2959 goalBottom -= getArrowScrollPreviewLength();
2960 }
2961
2962 if (viewToMakeVisible.getBottom() <= goalBottom) {
2963 // item is fully visible.
2964 return 0;
2965 }
2966
2967 if (nextSelectedPosition != INVALID_POSITION
2968 && (goalBottom - viewToMakeVisible.getTop()) >= getMaxScrollAmount()) {
2969 // item already has enough of it visible, changing selection is good enough
2970 return 0;
2971 }
2972
2973 int amountToScroll = (viewToMakeVisible.getBottom() - goalBottom);
2974
2975 if ((mFirstPosition + numChildren) == mItemCount) {
2976 // last is last in list -> make sure we don't scroll past it
2977 final int max = getChildAt(numChildren - 1).getBottom() - listBottom;
2978 amountToScroll = Math.min(amountToScroll, max);
2979 }
2980
2981 return Math.min(amountToScroll, getMaxScrollAmount());
2982 } else {
2983 int indexToMakeVisible = 0;
2984 if (nextSelectedPosition != INVALID_POSITION) {
2985 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2986 }
Justin Ho1ad11b92013-02-25 16:09:20 -08002987 while (indexToMakeVisible < 0) {
2988 // Child to view is not attached yet.
2989 addViewAbove(getChildAt(0), mFirstPosition);
2990 mFirstPosition--;
2991 indexToMakeVisible = nextSelectedPosition - mFirstPosition;
2992 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002993 final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
2994 final View viewToMakeVisible = getChildAt(indexToMakeVisible);
2995 int goalTop = listTop;
2996 if (positionToMakeVisible > 0) {
2997 goalTop += getArrowScrollPreviewLength();
2998 }
2999 if (viewToMakeVisible.getTop() >= goalTop) {
3000 // item is fully visible.
3001 return 0;
3002 }
3003
3004 if (nextSelectedPosition != INVALID_POSITION &&
3005 (viewToMakeVisible.getBottom() - goalTop) >= getMaxScrollAmount()) {
3006 // item already has enough of it visible, changing selection is good enough
3007 return 0;
3008 }
3009
3010 int amountToScroll = (goalTop - viewToMakeVisible.getTop());
3011 if (mFirstPosition == 0) {
3012 // first is first in list -> make sure we don't scroll past it
3013 final int max = listTop - getChildAt(0).getTop();
3014 amountToScroll = Math.min(amountToScroll, max);
3015 }
3016 return Math.min(amountToScroll, getMaxScrollAmount());
3017 }
3018 }
3019
3020 /**
3021 * Holds results of focus aware arrow scrolling.
3022 */
3023 static private class ArrowScrollFocusResult {
3024 private int mSelectedPosition;
3025 private int mAmountToScroll;
3026
3027 /**
3028 * How {@link android.widget.ListView#arrowScrollFocused} returns its values.
3029 */
3030 void populate(int selectedPosition, int amountToScroll) {
3031 mSelectedPosition = selectedPosition;
3032 mAmountToScroll = amountToScroll;
3033 }
3034
3035 public int getSelectedPosition() {
3036 return mSelectedPosition;
3037 }
3038
3039 public int getAmountToScroll() {
3040 return mAmountToScroll;
3041 }
3042 }
3043
3044 /**
3045 * @param direction either {@link android.view.View#FOCUS_UP} or
3046 * {@link android.view.View#FOCUS_DOWN}.
3047 * @return The position of the next selectable position of the views that
3048 * are currently visible, taking into account the fact that there might
3049 * be no selection. Returns {@link #INVALID_POSITION} if there is no
3050 * selectable view on screen in the given direction.
3051 */
3052 private int lookForSelectablePositionOnScreen(int direction) {
3053 final int firstPosition = mFirstPosition;
3054 if (direction == View.FOCUS_DOWN) {
3055 int startPos = (mSelectedPosition != INVALID_POSITION) ?
3056 mSelectedPosition + 1 :
3057 firstPosition;
3058 if (startPos >= mAdapter.getCount()) {
3059 return INVALID_POSITION;
3060 }
3061 if (startPos < firstPosition) {
3062 startPos = firstPosition;
3063 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003064
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003065 final int lastVisiblePos = getLastVisiblePosition();
3066 final ListAdapter adapter = getAdapter();
3067 for (int pos = startPos; pos <= lastVisiblePos; pos++) {
3068 if (adapter.isEnabled(pos)
3069 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
3070 return pos;
3071 }
3072 }
3073 } else {
3074 int last = firstPosition + getChildCount() - 1;
3075 int startPos = (mSelectedPosition != INVALID_POSITION) ?
3076 mSelectedPosition - 1 :
3077 firstPosition + getChildCount() - 1;
Dianne Hackborn5d9d03a2011-01-24 13:15:09 -08003078 if (startPos < 0 || startPos >= mAdapter.getCount()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003079 return INVALID_POSITION;
3080 }
3081 if (startPos > last) {
3082 startPos = last;
3083 }
3084
3085 final ListAdapter adapter = getAdapter();
3086 for (int pos = startPos; pos >= firstPosition; pos--) {
3087 if (adapter.isEnabled(pos)
3088 && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
3089 return pos;
3090 }
3091 }
3092 }
3093 return INVALID_POSITION;
3094 }
3095
3096 /**
3097 * Do an arrow scroll based on focus searching. If a new view is
3098 * given focus, return the selection delta and amount to scroll via
3099 * an {@link ArrowScrollFocusResult}, otherwise, return null.
3100 *
3101 * @param direction either {@link android.view.View#FOCUS_UP} or
3102 * {@link android.view.View#FOCUS_DOWN}.
3103 * @return The result if focus has changed, or <code>null</code>.
3104 */
3105 private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
3106 final View selectedView = getSelectedView();
3107 View newFocus;
3108 if (selectedView != null && selectedView.hasFocus()) {
3109 View oldFocus = selectedView.findFocus();
3110 newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
3111 } else {
3112 if (direction == View.FOCUS_DOWN) {
3113 final boolean topFadingEdgeShowing = (mFirstPosition > 0);
3114 final int listTop = mListPadding.top +
3115 (topFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3116 final int ySearchPoint =
3117 (selectedView != null && selectedView.getTop() > listTop) ?
3118 selectedView.getTop() :
3119 listTop;
3120 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3121 } else {
3122 final boolean bottomFadingEdgeShowing =
3123 (mFirstPosition + getChildCount() - 1) < mItemCount;
3124 final int listBottom = getHeight() - mListPadding.bottom -
3125 (bottomFadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
3126 final int ySearchPoint =
3127 (selectedView != null && selectedView.getBottom() < listBottom) ?
3128 selectedView.getBottom() :
3129 listBottom;
3130 mTempRect.set(0, ySearchPoint, 0, ySearchPoint);
3131 }
3132 newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
3133 }
3134
3135 if (newFocus != null) {
3136 final int positionOfNewFocus = positionOfNewFocus(newFocus);
3137
3138 // if the focus change is in a different new position, make sure
3139 // we aren't jumping over another selectable position
3140 if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
3141 final int selectablePosition = lookForSelectablePositionOnScreen(direction);
3142 if (selectablePosition != INVALID_POSITION &&
3143 ((direction == View.FOCUS_DOWN && selectablePosition < positionOfNewFocus) ||
3144 (direction == View.FOCUS_UP && selectablePosition > positionOfNewFocus))) {
3145 return null;
3146 }
3147 }
3148
3149 int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
3150
3151 final int maxScrollAmount = getMaxScrollAmount();
3152 if (focusScroll < maxScrollAmount) {
3153 // not moving too far, safe to give next view focus
3154 newFocus.requestFocus(direction);
3155 mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
3156 return mArrowScrollFocusResult;
3157 } else if (distanceToView(newFocus) < maxScrollAmount){
3158 // Case to consider:
3159 // too far to get entire next focusable on screen, but by going
3160 // max scroll amount, we are getting it at least partially in view,
3161 // so give it focus and scroll the max ammount.
3162 newFocus.requestFocus(direction);
3163 mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
3164 return mArrowScrollFocusResult;
3165 }
3166 }
3167 return null;
3168 }
3169
3170 /**
3171 * @param newFocus The view that would have focus.
3172 * @return the position that contains newFocus
3173 */
3174 private int positionOfNewFocus(View newFocus) {
3175 final int numChildren = getChildCount();
3176 for (int i = 0; i < numChildren; i++) {
3177 final View child = getChildAt(i);
3178 if (isViewAncestorOf(newFocus, child)) {
3179 return mFirstPosition + i;
3180 }
3181 }
3182 throw new IllegalArgumentException("newFocus is not a child of any of the"
3183 + " children of the list!");
3184 }
3185
3186 /**
3187 * Return true if child is an ancestor of parent, (or equal to the parent).
3188 */
3189 private boolean isViewAncestorOf(View child, View parent) {
3190 if (child == parent) {
3191 return true;
3192 }
3193
3194 final ViewParent theParent = child.getParent();
3195 return (theParent instanceof ViewGroup) && isViewAncestorOf((View) theParent, parent);
3196 }
3197
3198 /**
3199 * Determine how much we need to scroll in order to get newFocus in view.
3200 * @param direction either {@link android.view.View#FOCUS_UP} or
3201 * {@link android.view.View#FOCUS_DOWN}.
3202 * @param newFocus The view that would take focus.
3203 * @param positionOfNewFocus The position of the list item containing newFocus
3204 * @return The amount to scroll. Note: this is always positive! Direction
3205 * needs to be taken into account when actually scrolling.
3206 */
3207 private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
3208 int amountToScroll = 0;
3209 newFocus.getDrawingRect(mTempRect);
3210 offsetDescendantRectToMyCoords(newFocus, mTempRect);
3211 if (direction == View.FOCUS_UP) {
3212 if (mTempRect.top < mListPadding.top) {
3213 amountToScroll = mListPadding.top - mTempRect.top;
3214 if (positionOfNewFocus > 0) {
3215 amountToScroll += getArrowScrollPreviewLength();
3216 }
3217 }
3218 } else {
3219 final int listBottom = getHeight() - mListPadding.bottom;
3220 if (mTempRect.bottom > listBottom) {
3221 amountToScroll = mTempRect.bottom - listBottom;
3222 if (positionOfNewFocus < mItemCount - 1) {
3223 amountToScroll += getArrowScrollPreviewLength();
3224 }
3225 }
3226 }
3227 return amountToScroll;
3228 }
3229
3230 /**
3231 * Determine the distance to the nearest edge of a view in a particular
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003232 * direction.
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003233 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003234 * @param descendant A descendant of this list.
3235 * @return The distance, or 0 if the nearest edge is already on screen.
3236 */
3237 private int distanceToView(View descendant) {
3238 int distance = 0;
3239 descendant.getDrawingRect(mTempRect);
3240 offsetDescendantRectToMyCoords(descendant, mTempRect);
3241 final int listBottom = mBottom - mTop - mListPadding.bottom;
3242 if (mTempRect.bottom < mListPadding.top) {
3243 distance = mListPadding.top - mTempRect.bottom;
3244 } else if (mTempRect.top > listBottom) {
3245 distance = mTempRect.top - listBottom;
3246 }
3247 return distance;
3248 }
3249
3250
3251 /**
3252 * Scroll the children by amount, adding a view at the end and removing
3253 * views that fall off as necessary.
3254 *
3255 * @param amount The amount (positive or negative) to scroll.
3256 */
Mathew Inwood978c6e22018-08-21 15:58:55 +01003257 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003258 private void scrollListItemsBy(int amount) {
3259 offsetChildrenTopAndBottom(amount);
3260
3261 final int listBottom = getHeight() - mListPadding.bottom;
3262 final int listTop = mListPadding.top;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003263 final AbsListView.RecycleBin recycleBin = mRecycler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003264
3265 if (amount < 0) {
3266 // shifted items up
3267
3268 // may need to pan views into the bottom space
3269 int numChildren = getChildCount();
3270 View last = getChildAt(numChildren - 1);
3271 while (last.getBottom() < listBottom) {
3272 final int lastVisiblePosition = mFirstPosition + numChildren - 1;
3273 if (lastVisiblePosition < mItemCount - 1) {
3274 last = addViewBelow(last, lastVisiblePosition);
3275 numChildren++;
3276 } else {
3277 break;
3278 }
3279 }
3280
3281 // may have brought in the last child of the list that is skinnier
3282 // than the fading edge, thereby leaving space at the end. need
3283 // to shift back
3284 if (last.getBottom() < listBottom) {
3285 offsetChildrenTopAndBottom(listBottom - last.getBottom());
3286 }
3287
3288 // top views may be panned off screen
3289 View first = getChildAt(0);
3290 while (first.getBottom() < listTop) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003291 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
3292 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003293 recycleBin.addScrapView(first, mFirstPosition);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003294 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003295 detachViewFromParent(first);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003296 first = getChildAt(0);
3297 mFirstPosition++;
3298 }
3299 } else {
3300 // shifted items down
3301 View first = getChildAt(0);
3302
3303 // may need to pan views into top
3304 while ((first.getTop() > listTop) && (mFirstPosition > 0)) {
3305 first = addViewAbove(first, mFirstPosition);
3306 mFirstPosition--;
3307 }
3308
3309 // may have brought the very first child of the list in too far and
3310 // need to shift it back
3311 if (first.getTop() > listTop) {
3312 offsetChildrenTopAndBottom(listTop - first.getTop());
3313 }
3314
3315 int lastIndex = getChildCount() - 1;
3316 View last = getChildAt(lastIndex);
3317
3318 // bottom view may be panned off screen
3319 while (last.getTop() > listBottom) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003320 AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
3321 if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
Dianne Hackborn079e2352010-10-18 17:02:43 -07003322 recycleBin.addScrapView(last, mFirstPosition+lastIndex);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07003323 }
Mattias Niklewski158d6b72011-02-02 15:52:37 +01003324 detachViewFromParent(last);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003325 last = getChildAt(--lastIndex);
3326 }
3327 }
Yigit Boyar9afbf9c2016-05-09 16:42:37 -07003328 recycleBin.fullyDetachScrapViews();
Yigit Boyarb742b872016-05-06 16:11:12 -07003329 removeUnusedFixedViews(mHeaderViewInfos);
3330 removeUnusedFixedViews(mFooterViewInfos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003331 }
3332
3333 private View addViewAbove(View theView, int position) {
3334 int abovePosition = position - 1;
Romain Guy21875052010-01-06 18:48:08 -08003335 View view = obtainView(abovePosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003336 int edgeOfNewChild = theView.getTop() - mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003337 setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
3338 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003339 return view;
3340 }
3341
3342 private View addViewBelow(View theView, int position) {
3343 int belowPosition = position + 1;
Romain Guy21875052010-01-06 18:48:08 -08003344 View view = obtainView(belowPosition, mIsScrap);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003345 int edgeOfNewChild = theView.getBottom() + mDividerHeight;
Romain Guy21875052010-01-06 18:48:08 -08003346 setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
3347 false, mIsScrap[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003348 return view;
3349 }
3350
3351 /**
3352 * Indicates that the views created by the ListAdapter can contain focusable
3353 * items.
3354 *
3355 * @param itemsCanFocus true if items can get focus, false otherwise
3356 */
3357 public void setItemsCanFocus(boolean itemsCanFocus) {
3358 mItemsCanFocus = itemsCanFocus;
3359 if (!itemsCanFocus) {
3360 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
3361 }
3362 }
3363
3364 /**
3365 * @return Whether the views created by the ListAdapter can contain focusable
3366 * items.
3367 */
3368 public boolean getItemsCanFocus() {
3369 return mItemsCanFocus;
3370 }
3371
3372 @Override
Romain Guy24443ea2009-05-11 11:56:30 -07003373 public boolean isOpaque() {
Chet Haase78400552011-03-03 08:18:17 -08003374 boolean retValue = (mCachingActive && mIsCacheColorOpaque && mDividerIsOpaque &&
Romain Guy8f1344f52009-05-15 16:03:59 -07003375 hasOpaqueScrollbars()) || super.isOpaque();
Chet Haase78400552011-03-03 08:18:17 -08003376 if (retValue) {
3377 // only return true if the list items cover the entire area of the view
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003378 final int listTop = mListPadding != null ? mListPadding.top : mPaddingTop;
Chet Haase78400552011-03-03 08:18:17 -08003379 View first = getChildAt(0);
3380 if (first == null || first.getTop() > listTop) {
3381 return false;
3382 }
Adam Powell3ba8f5d62011-03-07 15:36:33 -08003383 final int listBottom = getHeight() -
3384 (mListPadding != null ? mListPadding.bottom : mPaddingBottom);
Chet Haase78400552011-03-03 08:18:17 -08003385 View last = getChildAt(getChildCount() - 1);
3386 if (last == null || last.getBottom() < listBottom) {
3387 return false;
3388 }
3389 }
3390 return retValue;
Romain Guy24443ea2009-05-11 11:56:30 -07003391 }
3392
3393 @Override
3394 public void setCacheColorHint(int color) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003395 final boolean opaque = (color >>> 24) == 0xFF;
3396 mIsCacheColorOpaque = opaque;
3397 if (opaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003398 if (mDividerPaint == null) {
3399 mDividerPaint = new Paint();
3400 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003401 mDividerPaint.setColor(color);
3402 }
Romain Guy24443ea2009-05-11 11:56:30 -07003403 super.setCacheColorHint(color);
3404 }
Adam Powell637d3372010-08-25 14:37:03 -07003405
3406 void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
3407 final int height = drawable.getMinimumHeight();
3408
3409 canvas.save();
3410 canvas.clipRect(bounds);
3411
3412 final int span = bounds.bottom - bounds.top;
3413 if (span < height) {
3414 bounds.top = bounds.bottom - height;
3415 }
3416
3417 drawable.setBounds(bounds);
3418 drawable.draw(canvas);
3419
3420 canvas.restore();
3421 }
3422
3423 void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
3424 final int height = drawable.getMinimumHeight();
3425
3426 canvas.save();
3427 canvas.clipRect(bounds);
3428
3429 final int span = bounds.bottom - bounds.top;
3430 if (span < height) {
3431 bounds.bottom = bounds.top + height;
3432 }
3433
3434 drawable.setBounds(bounds);
3435 drawable.draw(canvas);
3436
3437 canvas.restore();
3438 }
3439
Romain Guy24443ea2009-05-11 11:56:30 -07003440 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003441 protected void dispatchDraw(Canvas canvas) {
Romain Guy0211a0a2011-02-14 16:34:59 -08003442 if (mCachingStarted) {
3443 mCachingActive = true;
3444 }
3445
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003446 // Draw the dividers
3447 final int dividerHeight = mDividerHeight;
Adam Powell637d3372010-08-25 14:37:03 -07003448 final Drawable overscrollHeader = mOverScrollHeader;
3449 final Drawable overscrollFooter = mOverScrollFooter;
3450 final boolean drawOverscrollHeader = overscrollHeader != null;
3451 final boolean drawOverscrollFooter = overscrollFooter != null;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003452 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003453
Adam Powell637d3372010-08-25 14:37:03 -07003454 if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003455 // Only modify the top and bottom in the loop, we set the left and right here
3456 final Rect bounds = mTempRect;
3457 bounds.left = mPaddingLeft;
3458 bounds.right = mRight - mLeft - mPaddingRight;
3459
3460 final int count = getChildCount();
Michael Kwan744be162016-07-22 18:37:31 -07003461 final int headerCount = getHeaderViewsCount();
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003462 final int itemCount = mItemCount;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003463 final int footerLimit = (itemCount - mFooterViewInfos.size());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003464 final boolean headerDividers = mHeaderDividersEnabled;
3465 final boolean footerDividers = mFooterDividersEnabled;
3466 final int first = mFirstPosition;
Romain Guy2bed2272009-03-24 18:23:21 -07003467 final boolean areAllItemsSelectable = mAreAllItemsSelectable;
3468 final ListAdapter adapter = mAdapter;
Romain Guye32edc62009-05-29 10:33:36 -07003469 // If the list is opaque *and* the background is not, we want to
3470 // fill a rect where the dividers would be for non-selectable items
3471 // If the list is opaque and the background is also opaque, we don't
3472 // need to draw anything since the background will do it for us
Romain Guy179de8a2010-07-09 13:27:00 -07003473 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
Romain Guye32edc62009-05-29 10:33:36 -07003474
3475 if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
Romain Guya02903f2009-05-23 13:26:46 -07003476 mDividerPaint = new Paint();
Romain Guye32edc62009-05-29 10:33:36 -07003477 mDividerPaint.setColor(getCacheColorHint());
Romain Guya02903f2009-05-23 13:26:46 -07003478 }
Romain Guy8f1344f52009-05-15 16:03:59 -07003479 final Paint paint = mDividerPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003480
Adam Powell94566552011-01-05 23:25:33 -08003481 int effectivePaddingTop = 0;
3482 int effectivePaddingBottom = 0;
3483 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
3484 effectivePaddingTop = mListPadding.top;
3485 effectivePaddingBottom = mListPadding.bottom;
3486 }
3487
3488 final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003489 if (!mStackFromBottom) {
Adam Powell637d3372010-08-25 14:37:03 -07003490 int bottom = 0;
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003491
Adam Powell637d3372010-08-25 14:37:03 -07003492 // Draw top divider or header for overscroll
3493 final int scrollY = mScrollY;
3494 if (count > 0 && scrollY < 0) {
3495 if (drawOverscrollHeader) {
3496 bounds.bottom = 0;
3497 bounds.top = scrollY;
3498 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3499 } else if (drawDividers) {
3500 bounds.bottom = 0;
3501 bounds.top = -dividerHeight;
3502 drawDivider(canvas, bounds, -1);
3503 }
3504 }
3505
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003506 for (int i = 0; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003507 final int itemIndex = (first + i);
3508 final boolean isHeader = (itemIndex < headerCount);
3509 final boolean isFooter = (itemIndex >= footerLimit);
3510 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3511 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003512 bottom = child.getBottom();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003513 final boolean isLastItem = (i == (count - 1));
Adam Powell637d3372010-08-25 14:37:03 -07003514
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003515 if (drawDividers && (bottom < listBottom)
3516 && !(drawOverscrollFooter && isLastItem)) {
3517 final int nextIndex = (itemIndex + 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003518 // Draw dividers between enabled items, headers
3519 // and/or footers when enabled and requested, and
3520 // after the last enabled item.
3521 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3522 && (nextIndex >= headerCount)) && (isLastItem
3523 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
3524 && (nextIndex < footerLimit)))) {
Adam Powell637d3372010-08-25 14:37:03 -07003525 bounds.top = bottom;
3526 bounds.bottom = bottom + dividerHeight;
3527 drawDivider(canvas, bounds, i);
3528 } else if (fillForMissingDividers) {
3529 bounds.top = bottom;
3530 bounds.bottom = bottom + dividerHeight;
3531 canvas.drawRect(bounds, paint);
3532 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003533 }
3534 }
3535 }
Adam Powell637d3372010-08-25 14:37:03 -07003536
3537 final int overFooterBottom = mBottom + mScrollY;
3538 if (drawOverscrollFooter && first + count == itemCount &&
3539 overFooterBottom > bottom) {
3540 bounds.top = bottom;
3541 bounds.bottom = overFooterBottom;
3542 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3543 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003544 } else {
3545 int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003546
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003547 final int scrollY = mScrollY;
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003548
Adam Powell637d3372010-08-25 14:37:03 -07003549 if (count > 0 && drawOverscrollHeader) {
3550 bounds.top = scrollY;
3551 bounds.bottom = getChildAt(0).getTop();
3552 drawOverscrollHeader(canvas, overscrollHeader, bounds);
3553 }
3554
3555 final int start = drawOverscrollHeader ? 1 : 0;
3556 for (int i = start; i < count; i++) {
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003557 final int itemIndex = (first + i);
3558 final boolean isHeader = (itemIndex < headerCount);
3559 final boolean isFooter = (itemIndex >= footerLimit);
3560 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
3561 final View child = getChildAt(i);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003562 top = child.getTop();
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003563 if (drawDividers && (top > effectivePaddingTop)) {
3564 final boolean isFirstItem = (i == start);
3565 final int previousIndex = (itemIndex - 1);
Alan Viverette20cc6052013-11-15 15:56:38 -08003566 // Draw dividers between enabled items, headers
3567 // and/or footers when enabled and requested, and
3568 // before the first enabled item.
3569 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
3570 && (previousIndex >= headerCount)) && (isFirstItem ||
3571 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
3572 && (previousIndex < footerLimit)))) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003573 bounds.top = top - dividerHeight;
3574 bounds.bottom = top;
Alan Viveretteb9fc4b82013-06-03 16:42:22 -07003575 // Give the method the child ABOVE the divider,
3576 // so we subtract one from our child position.
3577 // Give -1 when there is no child above the
Romain Guy8f1344f52009-05-15 16:03:59 -07003578 // divider.
3579 drawDivider(canvas, bounds, i - 1);
Romain Guye32edc62009-05-29 10:33:36 -07003580 } else if (fillForMissingDividers) {
Romain Guy8f1344f52009-05-15 16:03:59 -07003581 bounds.top = top - dividerHeight;
3582 bounds.bottom = top;
3583 canvas.drawRect(bounds, paint);
3584 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003585 }
3586 }
3587 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003588
Romain Guy179de8a2010-07-09 13:27:00 -07003589 if (count > 0 && scrollY > 0) {
Adam Powell637d3372010-08-25 14:37:03 -07003590 if (drawOverscrollFooter) {
3591 final int absListBottom = mBottom;
3592 bounds.top = absListBottom;
3593 bounds.bottom = absListBottom + scrollY;
3594 drawOverscrollFooter(canvas, overscrollFooter, bounds);
3595 } else if (drawDividers) {
3596 bounds.top = listBottom;
3597 bounds.bottom = listBottom + dividerHeight;
3598 drawDivider(canvas, bounds, -1);
3599 }
Adam Powellbfb5d4b2010-03-10 18:55:25 -08003600 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003601 }
3602 }
3603
3604 // Draw the indicators (these should be drawn above the dividers) and children
3605 super.dispatchDraw(canvas);
3606 }
3607
Romain Guy0211a0a2011-02-14 16:34:59 -08003608 @Override
3609 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
3610 boolean more = super.drawChild(canvas, child, drawingTime);
3611 if (mCachingActive && child.mCachingFailed) {
3612 mCachingActive = false;
3613 }
3614 return more;
3615 }
3616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003617 /**
3618 * Draws a divider for the given child in the given bounds.
3619 *
3620 * @param canvas The canvas to draw to.
3621 * @param bounds The bounds of the divider.
3622 * @param childIndex The index of child (of the View) above the divider.
3623 * This will be -1 if there is no child above the divider to be
3624 * drawn.
3625 */
3626 void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
3627 // This widget draws the same divider for all children
3628 final Drawable divider = mDivider;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003629
Romain Guy95930e12010-10-04 13:46:02 -07003630 divider.setBounds(bounds);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003631 divider.draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003632 }
3633
3634 /**
3635 * Returns the drawable that will be drawn between each item in the list.
3636 *
3637 * @return the current drawable drawn between list elements
Alan Viverette7b2f8642015-06-01 10:30:21 -07003638 * @attr ref R.styleable#ListView_divider
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003639 */
Alan Viverette7b2f8642015-06-01 10:30:21 -07003640 @Nullable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003641 public Drawable getDivider() {
3642 return mDivider;
3643 }
3644
3645 /**
Alan Viverette7b2f8642015-06-01 10:30:21 -07003646 * Sets the drawable that will be drawn between each item in the list.
3647 * <p>
3648 * <strong>Note:</strong> If the drawable does not have an intrinsic
3649 * height, you should also call {@link #setDividerHeight(int)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003650 *
Alan Viverette7b2f8642015-06-01 10:30:21 -07003651 * @param divider the drawable to use
3652 * @attr ref R.styleable#ListView_divider
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003653 */
Alan Viverette7b2f8642015-06-01 10:30:21 -07003654 public void setDivider(@Nullable Drawable divider) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003655 if (divider != null) {
3656 mDividerHeight = divider.getIntrinsicHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003657 } else {
3658 mDividerHeight = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003659 }
3660 mDivider = divider;
Romain Guy24443ea2009-05-11 11:56:30 -07003661 mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
Romain Guyeeb55e62010-12-01 18:46:07 -08003662 requestLayout();
3663 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003664 }
3665
3666 /**
3667 * @return Returns the height of the divider that will be drawn between each item in the list.
3668 */
3669 public int getDividerHeight() {
3670 return mDividerHeight;
3671 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003672
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003673 /**
3674 * Sets the height of the divider that will be drawn between each item in the list. Calling
3675 * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
3676 *
3677 * @param height The new height of the divider in pixels.
3678 */
3679 public void setDividerHeight(int height) {
3680 mDividerHeight = height;
Romain Guyeeb55e62010-12-01 18:46:07 -08003681 requestLayout();
3682 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003683 }
3684
3685 /**
3686 * Enables or disables the drawing of the divider for header views.
3687 *
3688 * @param headerDividersEnabled True to draw the headers, false otherwise.
3689 *
3690 * @see #setFooterDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003691 * @see #areHeaderDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003692 * @see #addHeaderView(android.view.View)
3693 */
3694 public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
3695 mHeaderDividersEnabled = headerDividersEnabled;
3696 invalidate();
3697 }
3698
3699 /**
Alan Viverette55421032013-06-11 17:50:56 -07003700 * @return Whether the drawing of the divider for header views is enabled
3701 *
3702 * @see #setHeaderDividersEnabled(boolean)
3703 */
3704 public boolean areHeaderDividersEnabled() {
3705 return mHeaderDividersEnabled;
3706 }
3707
3708 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003709 * Enables or disables the drawing of the divider for footer views.
3710 *
3711 * @param footerDividersEnabled True to draw the footers, false otherwise.
3712 *
3713 * @see #setHeaderDividersEnabled(boolean)
Alan Viverette55421032013-06-11 17:50:56 -07003714 * @see #areFooterDividersEnabled()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003715 * @see #addFooterView(android.view.View)
3716 */
3717 public void setFooterDividersEnabled(boolean footerDividersEnabled) {
3718 mFooterDividersEnabled = footerDividersEnabled;
3719 invalidate();
3720 }
Alan Viverette55421032013-06-11 17:50:56 -07003721
3722 /**
3723 * @return Whether the drawing of the divider for footer views is enabled
3724 *
3725 * @see #setFooterDividersEnabled(boolean)
3726 */
3727 public boolean areFooterDividersEnabled() {
3728 return mFooterDividersEnabled;
3729 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003730
Adam Powell637d3372010-08-25 14:37:03 -07003731 /**
3732 * Sets the drawable that will be drawn above all other list content.
3733 * This area can become visible when the user overscrolls the list.
3734 *
3735 * @param header The drawable to use
3736 */
3737 public void setOverscrollHeader(Drawable header) {
3738 mOverScrollHeader = header;
3739 if (mScrollY < 0) {
3740 invalidate();
3741 }
3742 }
3743
3744 /**
3745 * @return The drawable that will be drawn above all other list content
3746 */
3747 public Drawable getOverscrollHeader() {
3748 return mOverScrollHeader;
3749 }
3750
3751 /**
3752 * Sets the drawable that will be drawn below all other list content.
3753 * This area can become visible when the user overscrolls the list,
3754 * or when the list's content does not fully fill the container area.
3755 *
3756 * @param footer The drawable to use
3757 */
3758 public void setOverscrollFooter(Drawable footer) {
3759 mOverScrollFooter = footer;
3760 invalidate();
3761 }
3762
3763 /**
3764 * @return The drawable that will be drawn below all other list content
3765 */
3766 public Drawable getOverscrollFooter() {
3767 return mOverScrollFooter;
3768 }
3769
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003770 @Override
3771 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
3772 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
3773
Adam Powelle1bf4862011-09-02 16:56:20 -07003774 final ListAdapter adapter = mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003775 int closetChildIndex = -1;
Adam Powelldcce1212011-10-31 16:41:21 -07003776 int closestChildTop = 0;
Adam Powelle1bf4862011-09-02 16:56:20 -07003777 if (adapter != null && gainFocus && previouslyFocusedRect != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003778 previouslyFocusedRect.offset(mScrollX, mScrollY);
3779
Adam Powelld7507832010-02-18 15:40:33 -08003780 // Don't cache the result of getChildCount or mFirstPosition here,
3781 // it could change in layoutChildren.
3782 if (adapter.getCount() < getChildCount() + mFirstPosition) {
Adam Powellc854f282009-12-16 14:11:53 -08003783 mLayoutMode = LAYOUT_NORMAL;
3784 layoutChildren();
3785 }
3786
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003787 // figure out which item should be selected based on previously
3788 // focused rect
3789 Rect otherRect = mTempRect;
3790 int minDistance = Integer.MAX_VALUE;
3791 final int childCount = getChildCount();
Adam Powelld7507832010-02-18 15:40:33 -08003792 final int firstPosition = mFirstPosition;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793
3794 for (int i = 0; i < childCount; i++) {
3795 // only consider selectable views
3796 if (!adapter.isEnabled(firstPosition + i)) {
3797 continue;
3798 }
3799
3800 View other = getChildAt(i);
3801 other.getDrawingRect(otherRect);
3802 offsetDescendantRectToMyCoords(other, otherRect);
3803 int distance = getDistance(previouslyFocusedRect, otherRect, direction);
3804
3805 if (distance < minDistance) {
3806 minDistance = distance;
3807 closetChildIndex = i;
Adam Powelldcce1212011-10-31 16:41:21 -07003808 closestChildTop = other.getTop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003809 }
3810 }
3811 }
3812
3813 if (closetChildIndex >= 0) {
Adam Powelldcce1212011-10-31 16:41:21 -07003814 setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003815 } else {
3816 requestLayout();
3817 }
3818 }
3819
3820
3821 /*
3822 * (non-Javadoc)
3823 *
3824 * Children specified in XML are assumed to be header views. After we have
3825 * parsed them move them out of the children list and into mHeaderViews.
3826 */
3827 @Override
3828 protected void onFinishInflate() {
3829 super.onFinishInflate();
3830
3831 int count = getChildCount();
3832 if (count > 0) {
3833 for (int i = 0; i < count; ++i) {
3834 addHeaderView(getChildAt(i));
3835 }
3836 removeAllViews();
3837 }
3838 }
3839
Alan Viverette06c2fff2017-01-27 11:29:02 -05003840 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003841 * @see android.view.View#findViewById(int)
Alan Viverette06c2fff2017-01-27 11:29:02 -05003842 * @removed For internal use only. This should have been hidden.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003843 */
3844 @Override
Alan Viverette8e1a7292017-02-27 10:57:58 -05003845 protected <T extends View> T findViewTraversal(@IdRes int id) {
3846 // First look in our children, then in any header and footer views that
3847 // may be scrolled off.
3848 View v = super.findViewTraversal(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003849 if (v == null) {
3850 v = findViewInHeadersOrFooters(mHeaderViewInfos, id);
3851 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003852 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003853 }
3854 v = findViewInHeadersOrFooters(mFooterViewInfos, id);
3855 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003856 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003857 }
3858 }
Alan Viverette8e1a7292017-02-27 10:57:58 -05003859 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003860 }
3861
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003862 View findViewInHeadersOrFooters(ArrayList<FixedViewInfo> where, int id) {
Alan Viverette06c2fff2017-01-27 11:29:02 -05003863 // Look in the passed in list of headers or footers for the view.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003864 if (where != null) {
3865 int len = where.size();
3866 View v;
3867
3868 for (int i = 0; i < len; i++) {
3869 v = where.get(i).view;
3870
3871 if (!v.isRootNamespace()) {
3872 v = v.findViewById(id);
3873
3874 if (v != null) {
3875 return v;
3876 }
3877 }
3878 }
3879 }
3880 return null;
3881 }
3882
Alan Viverette06c2fff2017-01-27 11:29:02 -05003883 /**
Jeff Brown4e6319b2010-12-13 10:36:51 -08003884 * @see android.view.View#findViewWithTag(Object)
Alan Viverette06c2fff2017-01-27 11:29:02 -05003885 * @removed For internal use only. This should have been hidden.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003886 */
3887 @Override
Alan Viverette8e1a7292017-02-27 10:57:58 -05003888 protected <T extends View> T findViewWithTagTraversal(Object tag) {
3889 // First look in our children, then in any header and footer views that
3890 // may be scrolled off.
3891 View v = super.findViewWithTagTraversal(tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003892 if (v == null) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003893 v = findViewWithTagInHeadersOrFooters(mHeaderViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003894 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003895 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003896 }
3897
Jeff Brown4e6319b2010-12-13 10:36:51 -08003898 v = findViewWithTagInHeadersOrFooters(mFooterViewInfos, tag);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003899 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003900 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003901 }
3902 }
Alan Viverette8e1a7292017-02-27 10:57:58 -05003903 return (T) v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003904 }
3905
Jeff Brown4e6319b2010-12-13 10:36:51 -08003906 View findViewWithTagInHeadersOrFooters(ArrayList<FixedViewInfo> where, Object tag) {
Alan Viverette06c2fff2017-01-27 11:29:02 -05003907 // Look in the passed in list of headers or footers for the view with
3908 // the tag.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003909 if (where != null) {
3910 int len = where.size();
3911 View v;
3912
3913 for (int i = 0; i < len; i++) {
3914 v = where.get(i).view;
3915
3916 if (!v.isRootNamespace()) {
3917 v = v.findViewWithTag(tag);
3918
3919 if (v != null) {
3920 return v;
3921 }
3922 }
3923 }
3924 }
3925 return null;
3926 }
3927
Jeff Brown4e6319b2010-12-13 10:36:51 -08003928 /**
Alan Viverette06c2fff2017-01-27 11:29:02 -05003929 * First look in our children, then in any header and footer views that may
3930 * be scrolled off.
3931 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08003932 * @see android.view.View#findViewByPredicate(Predicate)
Alan Viverette06c2fff2017-01-27 11:29:02 -05003933 * @hide
Jeff Brown4e6319b2010-12-13 10:36:51 -08003934 */
3935 @Override
Alan Viverette8e1a7292017-02-27 10:57:58 -05003936 protected <T extends View> T findViewByPredicateTraversal(
3937 Predicate<View> predicate, View childToSkip) {
3938 View v = super.findViewByPredicateTraversal(predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003939 if (v == null) {
Jeff Brown4dfbec22011-08-15 14:55:37 -07003940 v = findViewByPredicateInHeadersOrFooters(mHeaderViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003941 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003942 return (T) v;
Jeff Brown4e6319b2010-12-13 10:36:51 -08003943 }
3944
Jeff Brown4dfbec22011-08-15 14:55:37 -07003945 v = findViewByPredicateInHeadersOrFooters(mFooterViewInfos, predicate, childToSkip);
Jeff Brown4e6319b2010-12-13 10:36:51 -08003946 if (v != null) {
Alan Viverette8e1a7292017-02-27 10:57:58 -05003947 return (T) v;
Jeff Brown4e6319b2010-12-13 10:36:51 -08003948 }
3949 }
Alan Viverette8e1a7292017-02-27 10:57:58 -05003950 return (T) v;
Jeff Brown4e6319b2010-12-13 10:36:51 -08003951 }
3952
Alan Viverette06c2fff2017-01-27 11:29:02 -05003953 /**
3954 * Look in the passed in list of headers or footers for the first view that
3955 * matches the predicate.
Jeff Brown4e6319b2010-12-13 10:36:51 -08003956 */
3957 View findViewByPredicateInHeadersOrFooters(ArrayList<FixedViewInfo> where,
Jeff Brown4dfbec22011-08-15 14:55:37 -07003958 Predicate<View> predicate, View childToSkip) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003959 if (where != null) {
3960 int len = where.size();
3961 View v;
3962
3963 for (int i = 0; i < len; i++) {
3964 v = where.get(i).view;
3965
Jeff Brown4dfbec22011-08-15 14:55:37 -07003966 if (v != childToSkip && !v.isRootNamespace()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08003967 v = v.findViewByPredicate(predicate);
3968
3969 if (v != null) {
3970 return v;
3971 }
3972 }
3973 }
3974 }
3975 return null;
3976 }
3977
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003978 /**
Gilles Debunnefd3ddfa2010-02-17 16:59:20 -08003979 * Returns the set of checked items ids. The result is only valid if the
Adam Powell8f1bfe12010-03-05 15:13:56 -08003980 * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003981 *
Adam Powell8f1bfe12010-03-05 15:13:56 -08003982 * @return A new array which contains the id of each checked item in the
3983 * list.
Aurimas Liutikas99441c52016-10-11 16:48:32 -07003984 *
Adam Powell463ceff2010-03-09 11:50:51 -08003985 * @deprecated Use {@link #getCheckedItemIds()} instead.
Adam Powell8f1bfe12010-03-05 15:13:56 -08003986 */
Adam Powell8350f7d2010-07-28 14:27:28 -07003987 @Deprecated
Adam Powell8f1bfe12010-03-05 15:13:56 -08003988 public long[] getCheckItemIds() {
Adam Powell463ceff2010-03-09 11:50:51 -08003989 // Use new behavior that correctly handles stable ID mapping.
3990 if (mAdapter != null && mAdapter.hasStableIds()) {
3991 return getCheckedItemIds();
3992 }
3993
3994 // Old behavior was buggy, but would sort of work for adapters without stable IDs.
3995 // Fall back to it to support legacy apps.
3996 if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
3997 final SparseBooleanArray states = mCheckStates;
3998 final int count = states.size();
3999 final long[] ids = new long[count];
4000 final ListAdapter adapter = mAdapter;
4001
4002 int checkedCount = 0;
4003 for (int i = 0; i < count; i++) {
4004 if (states.valueAt(i)) {
4005 ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
4006 }
4007 }
4008
4009 // Trim array if needed. mCheckStates may contain false values
4010 // resulting in checkedCount being smaller than count.
4011 if (checkedCount == count) {
4012 return ids;
4013 } else {
4014 final long[] result = new long[checkedCount];
4015 System.arraycopy(ids, 0, result, 0, checkedCount);
4016
4017 return result;
4018 }
4019 }
4020 return new long[0];
Adam Powell8f1bfe12010-03-05 15:13:56 -08004021 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08004022
4023 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +01004024 @UnsupportedAppUsage
Alan Viverette441b4372014-02-12 13:30:20 -08004025 int getHeightForPosition(int position) {
4026 final int height = super.getHeightForPosition(position);
4027 if (shouldAdjustHeightForDivider(position)) {
4028 return height + mDividerHeight;
4029 }
4030 return height;
4031 }
4032
4033 private boolean shouldAdjustHeightForDivider(int itemIndex) {
4034 final int dividerHeight = mDividerHeight;
4035 final Drawable overscrollHeader = mOverScrollHeader;
4036 final Drawable overscrollFooter = mOverScrollFooter;
4037 final boolean drawOverscrollHeader = overscrollHeader != null;
4038 final boolean drawOverscrollFooter = overscrollFooter != null;
4039 final boolean drawDividers = dividerHeight > 0 && mDivider != null;
4040
4041 if (drawDividers) {
4042 final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
4043 final int itemCount = mItemCount;
Michael Kwan744be162016-07-22 18:37:31 -07004044 final int headerCount = getHeaderViewsCount();
Alan Viverette441b4372014-02-12 13:30:20 -08004045 final int footerLimit = (itemCount - mFooterViewInfos.size());
4046 final boolean isHeader = (itemIndex < headerCount);
4047 final boolean isFooter = (itemIndex >= footerLimit);
4048 final boolean headerDividers = mHeaderDividersEnabled;
4049 final boolean footerDividers = mFooterDividersEnabled;
4050 if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
4051 final ListAdapter adapter = mAdapter;
4052 if (!mStackFromBottom) {
4053 final boolean isLastItem = (itemIndex == (itemCount - 1));
4054 if (!drawOverscrollFooter || !isLastItem) {
4055 final int nextIndex = itemIndex + 1;
4056 // Draw dividers between enabled items, headers
4057 // and/or footers when enabled and requested, and
4058 // after the last enabled item.
4059 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
4060 && (nextIndex >= headerCount)) && (isLastItem
4061 || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
4062 && (nextIndex < footerLimit)))) {
4063 return true;
4064 } else if (fillForMissingDividers) {
4065 return true;
4066 }
4067 }
4068 } else {
4069 final int start = drawOverscrollHeader ? 1 : 0;
4070 final boolean isFirstItem = (itemIndex == start);
4071 if (!isFirstItem) {
4072 final int previousIndex = (itemIndex - 1);
4073 // Draw dividers between enabled items, headers
4074 // and/or footers when enabled and requested, and
4075 // before the first enabled item.
4076 if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
4077 && (previousIndex >= headerCount)) && (isFirstItem ||
4078 adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
4079 && (previousIndex < footerLimit)))) {
4080 return true;
4081 } else if (fillForMissingDividers) {
4082 return true;
4083 }
4084 }
4085 }
4086 }
4087 }
4088
4089 return false;
4090 }
4091
4092 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08004093 public CharSequence getAccessibilityClassName() {
4094 return ListView.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08004095 }
4096
Alan Viverettea54956a2015-01-07 16:05:02 -08004097 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08004098 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08004099 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
4100 super.onInitializeAccessibilityNodeInfoInternal(info);
Alan Viverette5b2081d2013-08-28 10:43:07 -07004101
Alan Viverette77c180a2014-09-08 15:30:34 -07004102 final int rowsCount = getCount();
Alan Viverette76769ae2014-02-12 16:38:10 -08004103 final int selectionMode = getSelectionModeForAccessibility();
Alan Viverette77c180a2014-09-08 15:30:34 -07004104 final CollectionInfo collectionInfo = CollectionInfo.obtain(
4105 rowsCount, 1, false, selectionMode);
Alan Viverette5b2081d2013-08-28 10:43:07 -07004106 info.setCollectionInfo(collectionInfo);
Alan Viverette23f44322015-04-06 16:04:56 -07004107
4108 if (rowsCount > 0) {
4109 info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
4110 }
4111 }
4112
4113 /** @hide */
4114 @Override
4115 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4116 if (super.performAccessibilityActionInternal(action, arguments)) {
4117 return true;
4118 }
4119
4120 switch (action) {
4121 case R.id.accessibilityActionScrollToPosition: {
4122 final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
4123 final int position = Math.min(row, getCount() - 1);
4124 if (row >= 0) {
4125 // The accessibility service gets data asynchronously, so
4126 // we'll be a little lenient by clamping the last position.
4127 smoothScrollToPosition(position);
4128 return true;
4129 }
4130 } break;
4131 }
4132
4133 return false;
Alan Viverette5b2081d2013-08-28 10:43:07 -07004134 }
4135
4136 @Override
4137 public void onInitializeAccessibilityNodeInfoForItem(
4138 View view, int position, AccessibilityNodeInfo info) {
4139 super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
4140
4141 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
Steven Dao5f9eb892016-02-01 11:40:31 -08004142 final boolean isHeading = lp != null && lp.viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
Alan Viverette76769ae2014-02-12 16:38:10 -08004143 final boolean isSelected = isItemChecked(position);
4144 final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
Alan Viverette77c180a2014-09-08 15:30:34 -07004145 position, 1, 0, 1, isHeading, isSelected);
Alan Viverette5b2081d2013-08-28 10:43:07 -07004146 info.setCollectionItemInfo(itemInfo);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08004147 }
Siva Velusamy94a6d152015-05-05 15:07:00 -07004148
4149 /** @hide */
4150 @Override
4151 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
4152 super.encodeProperties(encoder);
4153
4154 encoder.addProperty("recycleOnMeasure", recycleOnMeasure());
4155 }
Michael Kwan744be162016-07-22 18:37:31 -07004156
4157 /** @hide */
4158 protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
4159 ArrayList<ListView.FixedViewInfo> headerViewInfos,
4160 ArrayList<ListView.FixedViewInfo> footerViewInfos,
4161 ListAdapter adapter) {
4162 return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
4163 }
4164
4165 /** @hide */
4166 protected void wrapHeaderListAdapterInternal() {
4167 mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
4168 }
4169
4170 /** @hide */
4171 protected void dispatchDataSetObserverOnChangedInternal() {
4172 if (mDataSetObserver != null) {
4173 mDataSetObserver.onChanged();
4174 }
4175 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004176}