Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-04-08 12:42:37 +0300
committerStefan Niedermann <info@niedermann.it>2020-04-08 12:42:37 +0300
commit35f20c292bb4f1f3c7b9aadc8427fd86358a64e9 (patch)
treea72f3cd5791fb144ab295149e8e12e7edf377618 /tab-layout-helper
parentaae206befa986e11f29b61d8761b1cf7b6599fa3 (diff)
Move TabLayoutHelper to own library
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'tab-layout-helper')
-rw-r--r--tab-layout-helper/.gitignore1
-rw-r--r--tab-layout-helper/build.gradle36
-rw-r--r--tab-layout-helper/consumer-rules.pro0
-rw-r--r--tab-layout-helper/proguard-rules.pro21
-rw-r--r--tab-layout-helper/src/main/AndroidManifest.xml1
-rw-r--r--tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/FixedTabLayoutOnPageChangeListener.java92
-rw-r--r--tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabLayoutHelper.java363
-rw-r--r--tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabTitleGenerator.java5
8 files changed, 519 insertions, 0 deletions
diff --git a/tab-layout-helper/.gitignore b/tab-layout-helper/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/tab-layout-helper/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tab-layout-helper/build.gradle b/tab-layout-helper/build.gradle
new file mode 100644
index 000000000..c9b3119fb
--- /dev/null
+++ b/tab-layout-helper/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation "androidx.viewpager2:viewpager2:1.0.0"
+ implementation 'com.google.android.material:material:1.1.0'
+}
diff --git a/tab-layout-helper/consumer-rules.pro b/tab-layout-helper/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tab-layout-helper/consumer-rules.pro
diff --git a/tab-layout-helper/proguard-rules.pro b/tab-layout-helper/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/tab-layout-helper/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/tab-layout-helper/src/main/AndroidManifest.xml b/tab-layout-helper/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..0e49f42ac
--- /dev/null
+++ b/tab-layout-helper/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="it.niedermann.android.tablayouthelper" />
diff --git a/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/FixedTabLayoutOnPageChangeListener.java b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/FixedTabLayoutOnPageChangeListener.java
new file mode 100644
index 000000000..c3d33d083
--- /dev/null
+++ b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/FixedTabLayoutOnPageChangeListener.java
@@ -0,0 +1,92 @@
+package it.niedermann.android.tablayouthelper;
+
+import android.util.Log;
+
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class FixedTabLayoutOnPageChangeListener extends ViewPager2.OnPageChangeCallback {
+ private final WeakReference<TabLayout> mTabLayoutRef;
+ private int mPreviousScrollState;
+ private int mScrollState;
+
+ FixedTabLayoutOnPageChangeListener(TabLayout tabLayout) {
+ mTabLayoutRef = new WeakReference<>(tabLayout);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mPreviousScrollState = mScrollState;
+ mScrollState = state;
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ final TabLayout tabLayout = mTabLayoutRef.get();
+ if (tabLayout != null && shouldUpdateScrollPosition()) {
+ // Update the scroll position, only update the text selection if we're being
+ // dragged (or we're settling after a drag)
+ tabLayout.setScrollPosition(position, positionOffset, true);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ final TabLayout tabLayout = mTabLayoutRef.get();
+ if (tabLayout != null && tabLayout.getSelectedTabPosition() != position) {
+ // Select the tab, only updating the indicator if we're not being dragged/settled
+ // (since onPageScrolled will handle that).
+ Internal.selectTab(tabLayout, tabLayout.getTabAt(position),
+ mScrollState == ViewPager2.SCROLL_STATE_IDLE);
+ }
+ }
+
+ private boolean shouldUpdateScrollPosition() {
+ return (mScrollState == ViewPager2.SCROLL_STATE_DRAGGING) ||
+ ((mScrollState == ViewPager2.SCROLL_STATE_SETTLING) && (mPreviousScrollState == ViewPager2.SCROLL_STATE_DRAGGING));
+ }
+
+ private static class Internal {
+ private static final Method mMethodSelectTab;
+
+ static {
+ mMethodSelectTab = getAccessiblePrivateMethod(TabLayout.class, "selectTab", TabLayout.Tab.class, boolean.class);
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static Method getAccessiblePrivateMethod(Class<?> targetClass, String methodName, Class<?>... params) throws RuntimeException {
+ try {
+ Method m = targetClass.getDeclaredMethod(methodName, params);
+ m.setAccessible(true);
+ return m;
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void selectTab(TabLayout tabLayout, TabLayout.Tab tab, boolean updateIndicator) {
+ try {
+ mMethodSelectTab.invoke(tabLayout, tab, updateIndicator);
+ } catch (IllegalAccessException e) {
+ Log.e(TabLayoutHelper.class.getCanonicalName(), e.getMessage(), new IllegalStateException(e));
+ } catch (InvocationTargetException e) {
+ throw handleInvocationTargetException(e);
+ }
+ }
+
+ private static RuntimeException handleInvocationTargetException(InvocationTargetException e) {
+ Throwable targetException = e.getTargetException();
+ if (targetException instanceof RuntimeException) {
+ throw (RuntimeException) targetException;
+ } else {
+ throw new IllegalStateException(targetException);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabLayoutHelper.java b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabLayoutHelper.java
new file mode 100644
index 000000000..2402fb1de
--- /dev/null
+++ b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabLayoutHelper.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2015 Haruki Hasegawa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.niedermann.android.tablayouthelper;
+
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+
+/**
+ * This is a fork of the android-tablayouthelper project to make it compatible with Viewpager2.
+ * See also https://github.com/h6ah4i/android-tablayouthelper/issues/13
+ */
+public class TabLayoutHelper {
+ private TabLayout mTabLayout;
+ private TabTitleGenerator mTabTitleGenerator;
+ private ViewPager2 mViewPager;
+
+ private TabLayout.OnTabSelectedListener mInternalOnTabSelectedListener;
+ private FixedTabLayoutOnPageChangeListener mInternalTabLayoutOnPageChangeListener;
+ private RecyclerView.AdapterDataObserver mInternalDataSetObserver;
+ private Runnable mAdjustTabModeRunnable;
+ private Runnable mSetTabsFromPagerAdapterRunnable;
+ private Runnable mUpdateScrollPositionRunnable;
+ private boolean mAutoAdjustTabMode = false;
+ private boolean mDuringSetTabsFromPagerAdapter;
+
+ /**
+ * Constructor.
+ *
+ * @param tabLayout TabLayout instance
+ * @param viewPager ViewPager2 instance
+ * @param tabTitleGenerator TabTitleGenerator instance
+ */
+ public TabLayoutHelper(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager, @NonNull TabTitleGenerator tabTitleGenerator) {
+ RecyclerView.Adapter adapter = viewPager.getAdapter();
+
+ if (adapter == null) {
+ throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
+ }
+
+ mTabLayout = tabLayout;
+ mViewPager = viewPager;
+ mTabTitleGenerator = tabTitleGenerator;
+
+
+ mInternalDataSetObserver = new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ handleOnDataSetChanged();
+ }
+ };
+
+ mInternalOnTabSelectedListener = new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ handleOnTabSelected(tab);
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+ // Do nothing
+ }
+ };
+
+ mInternalTabLayoutOnPageChangeListener = new FixedTabLayoutOnPageChangeListener(mTabLayout);
+
+
+ viewPager.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ setTabsFromPagerAdapter(mTabLayout, viewPager.getAdapter(), viewPager.getCurrentItem());
+ }
+ });
+
+ setupWithViewPager(mTabLayout, mViewPager);
+
+ setAutoAdjustTabModeEnabled(true);
+ }
+
+ /**
+ * Sets auto tab mode adjustment enabled
+ *
+ * @param enabled True for enabled, otherwise false.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void setAutoAdjustTabModeEnabled(boolean enabled) {
+ if (mAutoAdjustTabMode == enabled) {
+ return;
+ }
+ mAutoAdjustTabMode = enabled;
+
+ if (mAutoAdjustTabMode) {
+ adjustTabMode(-1);
+ } else {
+ cancelPendingAdjustTabMode();
+ }
+ }
+
+ /**
+ * Unregister internal listener objects, release object references, etc.
+ * This method should be called in order to avoid memory leaks.
+ */
+ public void release() {
+ cancelPendingAdjustTabMode();
+ cancelPendingSetTabsFromPagerAdapter();
+ cancelPendingUpdateScrollPosition();
+
+ if (mInternalDataSetObserver != null) {
+ mInternalDataSetObserver = null;
+ }
+ if (mInternalOnTabSelectedListener != null) {
+ mTabLayout.removeOnTabSelectedListener(mInternalOnTabSelectedListener);
+ mInternalOnTabSelectedListener = null;
+ }
+ if (mInternalTabLayoutOnPageChangeListener != null) {
+ mInternalTabLayoutOnPageChangeListener = null;
+ }
+ mViewPager = null;
+ mTabLayout = null;
+ }
+
+ /**
+ * Override this method if you want to use custom tab layout.
+ *
+ * @param tabLayout TabLayout
+ * @param position Position of the item
+ * @return TabLayout.Tab
+ */
+ private TabLayout.Tab onCreateTab(TabLayout tabLayout, int position) {
+ TabLayout.Tab tab = tabLayout.newTab();
+ tab.setText(mTabTitleGenerator.getTitle(position));
+ return tab;
+ }
+
+ public void setTabTitleGenerator(TabTitleGenerator mTabTitleGenerator) {
+ this.mTabTitleGenerator = mTabTitleGenerator;
+ }
+
+ /**
+ * Override this method if you want to use custom tab layout
+ *
+ * @param tab Tab
+ */
+ private void onUpdateTab(TabLayout.Tab tab) {
+ if (tab.getCustomView() == null) {
+ tab.setCustomView(null); // invokes update() method internally.
+ }
+ }
+
+ //
+ // internal methods
+ //
+ private void handleOnDataSetChanged() {
+ cancelPendingUpdateScrollPosition();
+ cancelPendingSetTabsFromPagerAdapter();
+
+ if (mSetTabsFromPagerAdapterRunnable == null) {
+ mSetTabsFromPagerAdapterRunnable = () -> setTabsFromPagerAdapter(mTabLayout, mViewPager.getAdapter(), mViewPager.getCurrentItem());
+ }
+
+ mTabLayout.post(mSetTabsFromPagerAdapterRunnable);
+ }
+
+ private void handleOnTabSelected(TabLayout.Tab tab) {
+ if (mDuringSetTabsFromPagerAdapter) {
+ return;
+ }
+ mViewPager.setCurrentItem(tab.getPosition());
+ cancelPendingUpdateScrollPosition();
+ }
+
+ private void cancelPendingAdjustTabMode() {
+ if (mAdjustTabModeRunnable != null) {
+ mTabLayout.removeCallbacks(mAdjustTabModeRunnable);
+ mAdjustTabModeRunnable = null;
+ }
+ }
+
+ private void cancelPendingSetTabsFromPagerAdapter() {
+ if (mSetTabsFromPagerAdapterRunnable != null) {
+ mTabLayout.removeCallbacks(mSetTabsFromPagerAdapterRunnable);
+ mSetTabsFromPagerAdapterRunnable = null;
+ }
+ }
+
+ private void cancelPendingUpdateScrollPosition() {
+ if (mUpdateScrollPositionRunnable != null) {
+ mTabLayout.removeCallbacks(mUpdateScrollPositionRunnable);
+ mUpdateScrollPositionRunnable = null;
+ }
+ }
+
+ private void adjustTabMode(final int prevScrollX) {
+ final int prevScrollXMinZero = prevScrollX < 0 ? mTabLayout.getScrollX() : prevScrollX;
+
+ if (mAdjustTabModeRunnable != null) {
+ return;
+ }
+
+ if (ViewCompat.isLaidOut(mTabLayout)) {
+ adjustTabModeInternal(mTabLayout, prevScrollXMinZero);
+ } else {
+ mAdjustTabModeRunnable = () -> {
+ mAdjustTabModeRunnable = null;
+ adjustTabModeInternal(mTabLayout, prevScrollXMinZero);
+ };
+ mTabLayout.post(mAdjustTabModeRunnable);
+ }
+ }
+
+ private TabLayout.Tab createNewTab(TabLayout tabLayout, int position) {
+ return onCreateTab(tabLayout, position);
+ }
+
+ private void setupWithViewPager(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager) {
+ final RecyclerView.Adapter adapter = viewPager.getAdapter();
+ if (adapter == null) {
+ throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
+ }
+
+ setTabsFromPagerAdapter(tabLayout, adapter, viewPager.getCurrentItem());
+ viewPager.getAdapter().registerAdapterDataObserver(mInternalDataSetObserver);
+ viewPager.registerOnPageChangeCallback(mInternalTabLayoutOnPageChangeListener);
+ tabLayout.addOnTabSelectedListener(mInternalOnTabSelectedListener);
+ }
+
+ private void setTabsFromPagerAdapter(@NonNull TabLayout tabLayout, @Nullable RecyclerView.Adapter adapter, final int currentItem) {
+ try {
+ mDuringSetTabsFromPagerAdapter = true;
+
+ int prevScrollX = tabLayout.getScrollX();
+
+ // remove all tabs
+ tabLayout.removeAllTabs();
+
+ // add tabs
+ if (adapter != null) {
+ int count = adapter.getItemCount();
+ for (int i = 0; i < count; i++) {
+ TabLayout.Tab tab = createNewTab(tabLayout, i);
+ tabLayout.addTab(tab, false);
+ updateTab(tab);
+ }
+
+ // select current tab
+ final int currentItemPosition = Math.min(currentItem, count - 1);
+ TabLayout.Tab tab = tabLayout.getTabAt(currentItemPosition);
+ if (currentItemPosition >= 0 && tab != null) {
+ tab.select();
+ }
+ }
+
+ // adjust tab mode & gravity
+ if (mAutoAdjustTabMode) {
+ adjustTabMode(prevScrollX);
+ } else {
+ // restore scroll position if needed
+ int curTabMode = tabLayout.getTabMode();
+ if (curTabMode == TabLayout.MODE_SCROLLABLE) {
+ tabLayout.scrollTo(prevScrollX, 0);
+ }
+ }
+ } finally {
+ mDuringSetTabsFromPagerAdapter = false;
+ }
+ }
+
+ private void updateTab(TabLayout.Tab tab) {
+ onUpdateTab(tab);
+ }
+
+ private int determineTabMode(@NonNull TabLayout tabLayout) {
+ LinearLayout slidingTabStrip = (LinearLayout) tabLayout.getChildAt(0);
+
+ int childCount = slidingTabStrip.getChildCount();
+
+ // NOTE: slidingTabStrip.getMeasuredWidth() method does not return correct width!
+ // Need to measure each tabs and calculate the sum of them.
+
+ int tabLayoutWidth = tabLayout.getMeasuredWidth() - tabLayout.getPaddingLeft() - tabLayout.getPaddingRight();
+ int tabLayoutHeight = tabLayout.getMeasuredHeight() - tabLayout.getPaddingTop() - tabLayout.getPaddingBottom();
+
+ if (childCount == 0) {
+ return TabLayout.MODE_FIXED;
+ }
+
+ int stripWidth = 0;
+ int maxWidthTab = 0;
+ int tabHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(tabLayoutHeight, View.MeasureSpec.EXACTLY);
+
+ for (int i = 0; i < childCount; i++) {
+ View tabView = slidingTabStrip.getChildAt(i);
+ tabView.measure(View.MeasureSpec.UNSPECIFIED, tabHeightMeasureSpec);
+ int tabWidth = tabView.getMeasuredWidth();
+ stripWidth += tabWidth;
+ maxWidthTab = Math.max(maxWidthTab, tabWidth);
+ }
+
+ return ((stripWidth < tabLayoutWidth) && (maxWidthTab < (tabLayoutWidth / childCount)))
+ ? TabLayout.MODE_FIXED : TabLayout.MODE_SCROLLABLE;
+ }
+
+ private void adjustTabModeInternal(@NonNull TabLayout tabLayout, int prevScrollX) {
+ int prevTabMode = tabLayout.getTabMode();
+
+ tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
+ tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
+
+ int newTabMode = determineTabMode(tabLayout);
+
+ cancelPendingUpdateScrollPosition();
+
+ if (newTabMode == TabLayout.MODE_FIXED) {
+ tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
+ tabLayout.setTabMode(TabLayout.MODE_FIXED);
+ } else {
+ LinearLayout slidingTabStrip = (LinearLayout) tabLayout.getChildAt(0);
+ slidingTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
+ if (prevTabMode == TabLayout.MODE_SCROLLABLE) {
+ // restore scroll position
+ tabLayout.scrollTo(prevScrollX, 0);
+ } else {
+ // scroll to current selected tab
+ mUpdateScrollPositionRunnable = () -> {
+ mUpdateScrollPositionRunnable = null;
+ updateScrollPosition();
+ };
+ mTabLayout.post(mUpdateScrollPositionRunnable);
+ }
+ }
+ }
+
+ private void updateScrollPosition() {
+ mTabLayout.setScrollPosition(mTabLayout.getSelectedTabPosition(), 0, false);
+ }
+}
diff --git a/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabTitleGenerator.java b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabTitleGenerator.java
new file mode 100644
index 000000000..1640fe21d
--- /dev/null
+++ b/tab-layout-helper/src/main/java/it/niedermann/android/tablayouthelper/TabTitleGenerator.java
@@ -0,0 +1,5 @@
+package it.niedermann.android.tablayouthelper;
+
+public interface TabTitleGenerator {
+ String getTitle(int position);
+} \ No newline at end of file