From 0363341f6c2e224d0916bd113988b3b3318dbb55 Mon Sep 17 00:00:00 2001 From: Yigit Boyar Date: Thu, 24 Mar 2016 17:44:28 -0700 Subject: [PATCH] Recover focus on size change in ListView This CL fixes a bug in ListView where if items are not selectable, not focusable(in terms of ListView flags) but get focus (because they can), ListView would not recover the focus properly although it would make the child visible. The major issue seems like AbsListView marks data set changed when the list resizes. It seems like a workaround for some other issue and the code is from 2009 so instead of changing it, I've decided to implement a workaround to minimize the potential of breaking something else. Bug: 27488391 Change-Id: I5babd00e97bba7cb8cc9cfd0697ef79dfae12b97 --- core/java/android/widget/ListView.java | 83 ++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 5b4a3685bbdc5..7658cc8949139 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -16,15 +16,14 @@ package android.widget; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Trace; +import com.google.android.collect.Lists; + import com.android.internal.R; import com.android.internal.util.Predicate; -import com.google.android.collect.Lists; import android.annotation.IdRes; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -33,6 +32,8 @@ import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Trace; import android.util.AttributeSet; import android.util.MathUtils; import android.util.SparseBooleanArray; @@ -1106,20 +1107,63 @@ public class ListView extends AbsListView { } private class FocusSelector implements Runnable { + // the selector is waiting to set selection on the list view + private static final int STATE_SET_SELECTION = 1; + // the selector set the selection on the list view, waiting for a layoutChildren pass + private static final int STATE_WAIT_FOR_LAYOUT = 2; + // the selector's selection has been honored and it is waiting to request focus on the + // target child. + private static final int STATE_REQUEST_FOCUS = 3; + + private int mAction; private int mPosition; private int mPositionTop; - - public FocusSelector setup(int position, int top) { + + FocusSelector setupForSetSelection(int position, int top) { mPosition = position; mPositionTop = top; + mAction = STATE_SET_SELECTION; return this; } - + public void run() { - setSelectionFromTop(mPosition, mPositionTop); + if (mAction == STATE_SET_SELECTION) { + setSelectionFromTop(mPosition, mPositionTop); + mAction = STATE_WAIT_FOR_LAYOUT; + } else if (mAction == STATE_REQUEST_FOCUS) { + final int childIndex = mPosition - mFirstPosition; + final View child = getChildAt(childIndex); + if (child != null) { + child.requestFocus(); + } + mAction = -1; + } + } + + @Nullable Runnable setupFocusIfValid(int position) { + if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) { + return null; + } + mAction = STATE_REQUEST_FOCUS; + return this; + } + + void onLayoutComplete() { + if (mAction == STATE_WAIT_FOR_LAYOUT) { + mAction = -1; + } } } - + + @Override + protected void onDetachedFromWindow() { + if (mFocusSelector != null) { + removeCallbacks(mFocusSelector); + mFocusSelector = null; + } + super.onDetachedFromWindow(); + } + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (getChildCount() > 0) { @@ -1132,7 +1176,7 @@ public class ListView extends AbsListView { if (mFocusSelector == null) { mFocusSelector = new FocusSelector(); } - post(mFocusSelector.setup(childPosition, top)); + post(mFocusSelector.setupForSetSelection(childPosition, top)); } } super.onSizeChanged(w, h, oldw, oldh); @@ -1672,7 +1716,21 @@ public class ListView extends AbsListView { adjustViewsUpOrDown(); break; case LAYOUT_SPECIFIC: - sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop); + final int selectedPosition = reconcileSelectedPosition(); + sel = fillSpecific(selectedPosition, mSpecificTop); + /** + * When ListView is resized, FocusSelector requests an async selection for the + * previously focused item to make sure it is still visible. If the item is not + * selectable, it won't regain focus so instead we call FocusSelector + * to directly request focus on the view after it is visible. + */ + if (sel == null && mFocusSelector != null) { + final Runnable focusRunnable = mFocusSelector + .setupFocusIfValid(selectedPosition); + if (focusRunnable != null) { + post(focusRunnable); + } + } break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); @@ -1812,6 +1870,9 @@ public class ListView extends AbsListView { invokeOnItemScrollListener(); } finally { + if (mFocusSelector != null) { + mFocusSelector.onLayoutComplete(); + } if (!blockLayoutRequests) { mBlockLayoutRequests = false; }