From 33caad469000f45d5ec9e8d8cf26351849222e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Matuszewski?= Date: Sat, 14 Dec 2019 16:05:36 +0100 Subject: [PATCH 1/5] make main page tabs scrollable --- .../newpipe/fragments/MainFragment.java | 3 +- .../newpipe/views/ScrollableTabLayout.java | 83 +++++++++++++++++++ app/src/main/res/layout/fragment_main.xml | 3 +- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index 720e0f216..88a4c9c63 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -30,6 +30,7 @@ import org.schabi.newpipe.settings.tabs.Tab; import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.views.ScrollableTabLayout; import java.util.ArrayList; import java.util.List; @@ -37,7 +38,7 @@ import java.util.List; public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener { private ViewPager viewPager; private SelectedTabsPagerAdapter pagerAdapter; - private TabLayout tabLayout; + private ScrollableTabLayout tabLayout; private List tabsList = new ArrayList<>(); private TabsManager tabsManager; diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java new file mode 100644 index 000000000..88b108052 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -0,0 +1,83 @@ +package org.schabi.newpipe.views; + +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; + +import androidx.annotation.NonNull; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayout.Tab; + +/** + * A TabLayout that is scrollable when tabs exceed its width. + */ +public class ScrollableTabLayout extends TabLayout { + private static final String TAG = ScrollableTabLayout.class.getSimpleName(); + + public ScrollableTabLayout(Context context) { + super(context); + } + + public ScrollableTabLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + setTabMode(TabLayout.MODE_FIXED); + resetMode(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + resetMode(); + } + + @Override + public void addTab(@NonNull Tab tab, int position, boolean setSelected) { + super.addTab(tab, position, setSelected); + + resetMode(); + } + + @Override + public void removeTabAt(int position) { + super.removeTabAt(position); + + resetMode(); + } + + private void resetMode() { + if (getTabCount() == 0 || getTabAt(0).view == null) return; + setTabMode(TabLayout.MODE_FIXED); + + int layoutWidth = getWidth(); + int minimumWidth = ((View) getTabAt(0).view).getMinimumWidth(); + if (minimumWidth * getTabCount() > layoutWidth) { + setTabMode(TabLayout.MODE_SCROLLABLE); + return; + } + + int actualWidth = 0; + for (int i = 0; i < getTabCount(); ++i) { + if (getTabAt(i).view == null) return; + actualWidth += ((View) getTabAt(i).view).getWidth(); + if (actualWidth > layoutWidth) { + setTabMode(TabLayout.MODE_SCROLLABLE); + return; + } + } + } +} diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 85614342d..1a2455691 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -6,12 +6,13 @@ android:layout_height="match_parent"> - Date: Sun, 15 Dec 2019 12:41:19 +0100 Subject: [PATCH 2/5] hide main page tab selector with single tab --- .../org/schabi/newpipe/views/ScrollableTabLayout.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java index 88b108052..ffbb804af 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -14,6 +14,7 @@ import com.google.android.material.tabs.TabLayout.Tab; /** * A TabLayout that is scrollable when tabs exceed its width. + * Hides when there are less than 2 tabs. */ public class ScrollableTabLayout extends TabLayout { private static final String TAG = ScrollableTabLayout.class.getSimpleName(); @@ -60,6 +61,13 @@ public class ScrollableTabLayout extends TabLayout { } private void resetMode() { + if (getTabCount() < 2) { + setVisibility(View.GONE); + return; + } else { + setVisibility(View.VISIBLE); + } + if (getTabCount() == 0 || getTabAt(0).view == null) return; setTabMode(TabLayout.MODE_FIXED); From b674cfec241acfa6006c3b13fb419ff23dbc2a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Matuszewski?= Date: Mon, 16 Dec 2019 00:11:54 +0100 Subject: [PATCH 3/5] simplify ScrollableTabLayout tabs width checking --- .../newpipe/views/ScrollableTabLayout.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java index ffbb804af..40c021cec 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -25,18 +25,21 @@ public class ScrollableTabLayout extends TabLayout { public ScrollableTabLayout(Context context, AttributeSet attrs) { super(context, attrs); + setTabMode(TabLayout.MODE_FIXED); } public ScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + setTabMode(TabLayout.MODE_FIXED); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); - setTabMode(TabLayout.MODE_FIXED); - resetMode(); + if (changed) { + resetMode(); + } } @Override @@ -68,21 +71,15 @@ public class ScrollableTabLayout extends TabLayout { setVisibility(View.VISIBLE); } - if (getTabCount() == 0 || getTabAt(0).view == null) return; + int layoutWidth = getWidth(); + if (layoutWidth == 0) return; + setTabMode(TabLayout.MODE_FIXED); - int layoutWidth = getWidth(); - int minimumWidth = ((View) getTabAt(0).view).getMinimumWidth(); - if (minimumWidth * getTabCount() > layoutWidth) { - setTabMode(TabLayout.MODE_SCROLLABLE); - return; - } - - int actualWidth = 0; + int tabsRequestedWidth = 0; for (int i = 0; i < getTabCount(); ++i) { - if (getTabAt(i).view == null) return; - actualWidth += ((View) getTabAt(i).view).getWidth(); - if (actualWidth > layoutWidth) { + tabsRequestedWidth += ((View) getTabAt(i).view).getMinimumWidth(); + if (tabsRequestedWidth > layoutWidth) { setTabMode(TabLayout.MODE_SCROLLABLE); return; } From 1393d3ad7f3f44b6dc8afeefd5696617699bc09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Matuszewski?= Date: Tue, 17 Dec 2019 19:59:29 +0100 Subject: [PATCH 4/5] fix ScrollableTabLayout content width calculation fix bug where only minimum width requested by tab was counted even if actual content was wider --- .../newpipe/views/ScrollableTabLayout.java | 90 +++++++++++++------ 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java index 40c021cec..6dd6411e1 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -5,7 +5,6 @@ import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.View; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; import androidx.annotation.NonNull; @@ -19,70 +18,111 @@ import com.google.android.material.tabs.TabLayout.Tab; public class ScrollableTabLayout extends TabLayout { private static final String TAG = ScrollableTabLayout.class.getSimpleName(); + private int layoutWidth = 0; + private int prevVisibility = View.GONE; + public ScrollableTabLayout(Context context) { super(context); } public ScrollableTabLayout(Context context, AttributeSet attrs) { super(context, attrs); - setTabMode(TabLayout.MODE_FIXED); } public ScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setTabMode(TabLayout.MODE_FIXED); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (changed) { - resetMode(); - } + remeasureTabs(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - resetMode(); + layoutWidth = w; } @Override public void addTab(@NonNull Tab tab, int position, boolean setSelected) { super.addTab(tab, position, setSelected); - resetMode(); + hasMultipleTabs(); + + // Adding a tab won't decrease total tabs' width so tabMode won't have to change to FIXED + if (getTabMode() != MODE_SCROLLABLE) { + remeasureTabs(); + } } @Override public void removeTabAt(int position) { super.removeTabAt(position); - resetMode(); + hasMultipleTabs(); + + // Removing a tab won't increase total tabs' width so tabMode won't have to change to SCROLLABLE + if (getTabMode() != MODE_FIXED) { + remeasureTabs(); + } } - private void resetMode() { - if (getTabCount() < 2) { - setVisibility(View.GONE); - return; - } else { - setVisibility(View.VISIBLE); - } + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); - int layoutWidth = getWidth(); + // Recheck content width in case some tabs have been added or removed while ScrollableTabLayout was invisible + // We don't have to check if it was GONE because then requestLayout() will be called + if (changedView == this) { + if (prevVisibility == View.INVISIBLE) { + remeasureTabs(); + } + prevVisibility = visibility; + } + } + + private void setMode(int mode) { + if (mode == getTabMode()) return; + + setTabMode(mode); + } + + /** + * Make ScrollableTabLayout not visible if there are less than two tabs + */ + private void hasMultipleTabs() { + if (getTabCount() > 1) { + setVisibility(View.VISIBLE); + } else { + setVisibility(View.GONE); + } + } + + /** + * Calculate minimal width required by tabs and set tabMode accordingly + */ + private void remeasureTabs() { + if (getVisibility() != View.VISIBLE) return; if (layoutWidth == 0) return; - setTabMode(TabLayout.MODE_FIXED); - - int tabsRequestedWidth = 0; - for (int i = 0; i < getTabCount(); ++i) { - tabsRequestedWidth += ((View) getTabAt(i).view).getMinimumWidth(); - if (tabsRequestedWidth > layoutWidth) { - setTabMode(TabLayout.MODE_SCROLLABLE); - return; + final int count = getTabCount(); + int contentWidth = 0; + for (int i = 0; i < count; i++) { + View child = getTabAt(i).view; + if (child.getVisibility() == View.VISIBLE) { + // Use tab's minimum requested width should actual content be too small + contentWidth += Math.max(child.getMinimumWidth(), child.getMeasuredWidth()); } } + + if (contentWidth > layoutWidth) { + setMode(TabLayout.MODE_SCROLLABLE); + } else { + setMode(TabLayout.MODE_FIXED); + } } } From 3625a38a234ebf8a92f2ac05c562c4440248b5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Matuszewski?= Date: Wed, 1 Jan 2020 14:12:45 +0100 Subject: [PATCH 5/5] improve code consistency in ScrollableTabLayout --- .../main/java/org/schabi/newpipe/views/ScrollableTabLayout.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java index 6dd6411e1..48327220a 100644 --- a/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java +++ b/app/src/main/java/org/schabi/newpipe/views/ScrollableTabLayout.java @@ -106,7 +106,7 @@ public class ScrollableTabLayout extends TabLayout { * Calculate minimal width required by tabs and set tabMode accordingly */ private void remeasureTabs() { - if (getVisibility() != View.VISIBLE) return; + if (prevVisibility != View.VISIBLE) return; if (layoutWidth == 0) return; final int count = getTabCount();