Fix the higlighted settings not work properly from search

Since the Andorid S, we introduce the CollapsingToolbarLayout
, the highlighted funcation might break for unkown reason.
I observed that broken cases with overlapping issue on tool bar.
The possible root cause is the interation bwtween
CoordinatorLayout v.s CollapsingToolbarLayout v.s Recycler
view.

This cl is definetly a workaround to prevent this issue.
I try to collapse the tool bar with an animation before
we start to scroll the list. This makes the overall transition smooth
but always collapse the tool bar.

Fix: 177968297
Test: Click a lot of search results, and screen should highlight
settings correctly.

Change-Id: Id9c32b642433dcc39c179a2cc83a06e77cc47888
This commit is contained in:
Tsung-Mao Fang
2021-02-24 16:37:39 +08:00
parent beefb25b48
commit ef87755c34
3 changed files with 37 additions and 14 deletions

View File

@@ -57,6 +57,8 @@ import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.search.Indexable; import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import com.google.android.material.appbar.AppBarLayout;
import java.util.UUID; import java.util.UUID;
/** /**
@@ -112,9 +114,8 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
@VisibleForTesting @VisibleForTesting
ViewGroup mPinnedHeaderFrameLayout; ViewGroup mPinnedHeaderFrameLayout;
private AppBarLayout mAppBarLayout;
private LayoutPreference mHeader; private LayoutPreference mHeader;
private View mEmptyView; private View mEmptyView;
private LinearLayoutManager mLayoutManager; private LinearLayoutManager mLayoutManager;
private ArrayMap<String, Preference> mPreferenceCache; private ArrayMap<String, Preference> mPreferenceCache;
@@ -145,6 +146,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
Bundle savedInstanceState) { Bundle savedInstanceState) {
final View root = super.onCreateView(inflater, container, savedInstanceState); final View root = super.onCreateView(inflater, container, savedInstanceState);
mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header); mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header);
mAppBarLayout = getActivity().findViewById(R.id.app_bar);
return root; return root;
} }
@@ -250,7 +252,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
return; return;
} }
if (mAdapter != null) { if (mAdapter != null) {
mAdapter.requestHighlight(getView(), getListView()); mAdapter.requestHighlight(getView(), getListView(), mAppBarLayout);
} }
} }

View File

@@ -39,10 +39,14 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.google.android.material.appbar.AppBarLayout;
public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter { public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter {
private static final String TAG = "HighlightableAdapter"; private static final String TAG = "HighlightableAdapter";
@VisibleForTesting @VisibleForTesting
static final long DELAY_COLLAPSE_DURATION_MILLIS = 300L;
@VisibleForTesting
static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 600L; static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 600L;
private static final long HIGHLIGHT_DURATION = 15000L; private static final long HIGHLIGHT_DURATION = 15000L;
private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L; private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
@@ -115,7 +119,7 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
View v = holder.itemView; View v = holder.itemView;
if (position == mHighlightPosition if (position == mHighlightPosition
&& (mHighlightKey != null && (mHighlightKey != null
&& TextUtils.equals(mHighlightKey, getItem(position).getKey()))) { && TextUtils.equals(mHighlightKey, getItem(position).getKey()))) {
// This position should be highlighted. If it's highlighted before - skip animation. // This position should be highlighted. If it's highlighted before - skip animation.
addHighlightBackground(v, !mFadeInAnimated); addHighlightBackground(v, !mFadeInAnimated);
} else if (Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) { } else if (Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) {
@@ -124,15 +128,26 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
} }
} }
public void requestHighlight(View root, RecyclerView recyclerView) { /**
* A function can highlight a specific setting in recycler view.
* note: Before highlighting a setting, screen collapses tool bar with an animation.
*/
public void requestHighlight(View root, RecyclerView recyclerView, AppBarLayout appBarLayout) {
if (mHighlightRequested || recyclerView == null || TextUtils.isEmpty(mHighlightKey)) { if (mHighlightRequested || recyclerView == null || TextUtils.isEmpty(mHighlightKey)) {
return; return;
} }
final int position = getPreferenceAdapterPosition(mHighlightKey);
if (position < 0) {
return;
}
if (appBarLayout != null) {
root.postDelayed(() -> {
appBarLayout.setExpanded(false, true);
}, DELAY_COLLAPSE_DURATION_MILLIS);
}
root.postDelayed(() -> { root.postDelayed(() -> {
final int position = getPreferenceAdapterPosition(mHighlightKey);
if (position < 0) {
return;
}
mHighlightRequested = true; mHighlightRequested = true;
recyclerView.smoothScrollToPosition(position); recyclerView.smoothScrollToPosition(position);
mHighlightPosition = position; mHighlightPosition = position;

View File

@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -43,6 +44,8 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.google.android.material.appbar.AppBarLayout;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -85,8 +88,11 @@ public class HighlightablePreferenceGroupAdapterTest {
@Test @Test
public void requestHighlight_hasKey_notHighlightedBefore_shouldRequest() { public void requestHighlight_hasKey_notHighlightedBefore_shouldRequest() {
mAdapter.requestHighlight(mRoot, mock(RecyclerView.class)); when(mAdapter.getPreferenceAdapterPosition(anyString())).thenReturn(1);
mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class));
verify(mRoot).postDelayed(any(),
eq(HighlightablePreferenceGroupAdapter.DELAY_COLLAPSE_DURATION_MILLIS));
verify(mRoot).postDelayed(any(), verify(mRoot).postDelayed(any(),
eq(HighlightablePreferenceGroupAdapter.DELAY_HIGHLIGHT_DURATION_MILLIS)); eq(HighlightablePreferenceGroupAdapter.DELAY_HIGHLIGHT_DURATION_MILLIS));
} }
@@ -95,21 +101,21 @@ public class HighlightablePreferenceGroupAdapterTest {
public void requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest() { public void requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest() {
ReflectionHelpers.setField(mAdapter, "mHighlightKey", null); ReflectionHelpers.setField(mAdapter, "mHighlightKey", null);
ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false); ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false);
mAdapter.requestHighlight(mRoot, mock(RecyclerView.class)); mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class));
ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY); ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY);
ReflectionHelpers.setField(mAdapter, "mHighlightRequested", true); ReflectionHelpers.setField(mAdapter, "mHighlightRequested", true);
mAdapter.requestHighlight(mRoot, mock(RecyclerView.class)); mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class));
ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY); ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY);
ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false); ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false);
mAdapter.requestHighlight(mRoot, null /* recyclerView */); mAdapter.requestHighlight(mRoot, null /* recyclerView */, mock(AppBarLayout.class));
verifyZeroInteractions(mRoot); verifyZeroInteractions(mRoot);
} }
@Test @Test
public void adjustInitialExpandedChildCount_invalidInput_shouldNotadjust() { public void adjustInitialExpandedChildCount_invalidInput_shouldNotAdjust() {
HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(null /* host */); HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(null /* host */);
HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment);
final Bundle args = new Bundle(); final Bundle args = new Bundle();