From 2b39438eba2f4a78c288fd8a5121ce0672c544d0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Sep 2019 16:10:57 +0700 Subject: [PATCH] Fix scrolling in main screen grid GridLayoutManager is buggy - https://issuetracker.google.com/issues/37067220: it randomly loses or incorrectly assigns focus when being scrolled via direction-based navigation. This commit reimplements onFocusSearchFailed() on top of scrollBy() to work around that problem. Ordinary touch-based navigation should not be affected. --- .../fragments/list/BaseListFragment.java | 3 +- .../newpipe/local/BaseLocalListFragment.java | 3 +- .../subscription/SubscriptionFragment.java | 3 +- .../newpipe/views/FixedGridLayoutManager.java | 59 +++++++++++++++ .../newpipe/views/NewPipeRecyclerView.java | 72 +++++++++++++++++++ .../giga/ui/fragment/MissionsFragment.java | 3 +- app/src/main/res/layout/fragment_kiosk.xml | 2 +- 7 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/FixedGridLayoutManager.java create mode 100644 app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index a3844a92f..88684f2e7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -35,6 +35,7 @@ import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.views.SuperScrollLayoutManager; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.util.List; import java.util.Queue; @@ -156,7 +157,7 @@ public abstract class BaseListFragment extends BaseStateFragment implem int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); width += (24 * resources.getDisplayMetrics().density); final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); - final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 414a9b6b5..c1293e240 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -18,6 +18,7 @@ import android.view.View; import org.schabi.newpipe.R; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.list.ListViewContract; +import org.schabi.newpipe.views.FixedGridLayoutManager; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -95,7 +96,7 @@ public abstract class BaseLocalListFragment extends BaseStateFragment int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); width += (24 * resources.getDisplayMetrics().density); final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); - final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + final GridLayoutManager lm = new FixedGridLayoutManager(activity, spanCount); lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); return lm; } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java index bff6c1b3a..ea820b71e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java @@ -57,6 +57,7 @@ import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.CollapsibleView; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.text.SimpleDateFormat; @@ -192,7 +193,7 @@ public class SubscriptionFragment extends BaseStateFragment + * FixedGridLayoutManager.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +// Version of GridLayoutManager that works around https://issuetracker.google.com/issues/37067220 +public class FixedGridLayoutManager extends GridLayoutManager { + public FixedGridLayoutManager(Context context, int spanCount) { + super(context, spanCount); + } + + public FixedGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public FixedGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { + super(context, spanCount, orientation, reverseLayout); + } + + @Override + public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) { + FocusFinder ff = FocusFinder.getInstance(); + + View result = ff.findNextFocus((ViewGroup) focused.getParent(), focused, focusDirection); + if (result != null) { + return super.onFocusSearchFailed(focused, focusDirection, recycler, state); + } + + if (focusDirection == View.FOCUS_DOWN) { + scrollVerticallyBy(10, recycler, state); + return null; + } + + return super.onFocusSearchFailed(focused, focusDirection, recycler, state); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java new file mode 100644 index 000000000..76dee200f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/NewPipeRecyclerView.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) Eltex ltd 2019 + * NewPipeRecyclerView.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +public class NewPipeRecyclerView extends RecyclerView { + private static final String TAG = "FixedRecyclerView"; + + public NewPipeRecyclerView(@NonNull Context context) { + super(context); + } + + public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public NewPipeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public View focusSearch(int direction) { + return null; + } + + @Override + public View focusSearch(View focused, int direction) { + return null; + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + View found = super.focusSearch(focused, direction); + if (found != null) { + found.requestFocus(direction); + return true; + } + + if (direction == View.FOCUS_UP) { + if (canScrollVertically(-1)) { + scrollBy(0, -10); + return true; + } + + return false; + } + + return super.dispatchUnhandledMove(focused, direction); + } +} diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 26da47b1f..3792f030a 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -30,6 +30,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.ThemeHelper; +import org.schabi.newpipe.views.FixedGridLayoutManager; import java.io.File; import java.io.IOException; @@ -108,7 +109,7 @@ public class MissionsFragment extends Fragment { mList = v.findViewById(R.id.mission_recycler); // Init layouts managers - mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE); + mGridManager = new FixedGridLayoutManager(getActivity(), SPAN_SIZE); mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { diff --git a/app/src/main/res/layout/fragment_kiosk.xml b/app/src/main/res/layout/fragment_kiosk.xml index 01eeb0855..643d7d4f0 100644 --- a/app/src/main/res/layout/fragment_kiosk.xml +++ b/app/src/main/res/layout/fragment_kiosk.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> -