com.qiscus.sdk.filepicker.util.TabLayoutHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.qiscus.sdk.filepicker.util.TabLayoutHelper.java

Source

/*
 * Copyright (c) 2016 Qiscus.
 *
 * 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 com.qiscus.sdk.filepicker.util;

import android.database.DataSetObserver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created on : March 16, 2017
 * Author     : zetbaitsu
 * Name       : Zetra
 * GitHub     : https://github.com/zetbaitsu
 */
public class TabLayoutHelper {
    private TabLayout tabLayout;
    private ViewPager viewPager;

    private TabLayout.OnTabSelectedListener userOnTabSelectedListener;

    private TabLayout.OnTabSelectedListener internalOnTabSelectedListener;
    private FixedTabLayoutOnPageChangeListener internalTabLayoutOnPageChangeListener;
    private ViewPager.OnAdapterChangeListener internalOnAdapterChangeListener;
    private DataSetObserver internalDataSetObserver;
    private Runnable adjustTabModeRunnable;
    private Runnable setTabsFromPagerAdapterRunnable;
    private Runnable updateScrollPositionRunnable;
    private boolean autoAdjustTabMode = false;
    private boolean duringSetTabsFromPagerAdapter;

    /**
     * Constructor.
     *
     * @param tabLayout TabLayout instance
     * @param viewPager ViewPager instance
     */
    public TabLayoutHelper(@NonNull TabLayout tabLayout, @NonNull ViewPager viewPager) {
        PagerAdapter adapter = viewPager.getAdapter();

        if (adapter == null) {
            throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
        }

        this.tabLayout = tabLayout;
        this.viewPager = viewPager;

        internalDataSetObserver = new DataSetObserver() {
            @Override
            public void onChanged() {
                handleOnDataSetChanged();
            }
        };

        internalOnTabSelectedListener = new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                handleOnTabSelected(tab);
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                handleOnTabUnselected(tab);
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                handleOnTabReselected(tab);
            }
        };

        internalTabLayoutOnPageChangeListener = new FixedTabLayoutOnPageChangeListener(this.tabLayout);

        internalOnAdapterChangeListener = this::handleOnAdapterChanged;

        setupWithViewPager(this.tabLayout, this.viewPager);
    }

    //
    // public methods
    //

    /**
     * Retrieve underlying TabLayout instance.
     *
     * @return TabLayout instance
     */
    public TabLayout getTabLayout() {
        return tabLayout;
    }

    /**
     * Retrieve ViewPager instance.
     *
     * @return ViewPager instance
     */
    public ViewPager getViewPager() {
        return viewPager;
    }

    /**
     * Sets auto tab mode adjustment enabled
     *
     * @param enabled True for enabled, otherwise false.
     */
    public void setAutoAdjustTabModeEnabled(boolean enabled) {
        if (autoAdjustTabMode == enabled) {
            return;
        }
        autoAdjustTabMode = enabled;

        if (autoAdjustTabMode) {
            adjustTabMode(-1);
        } else {
            cancelPendingAdjustTabMode();
        }
    }

    /**
     * Gets whether auto tab mode adjustment is enabled.
     *
     * @return True for enabled, otherwise false.
     */
    public boolean isAutoAdjustTabModeEnabled() {
        return autoAdjustTabMode;
    }

    @Deprecated
    public void setOnTabSelectedListener(TabLayout.OnTabSelectedListener listener) {
        userOnTabSelectedListener = listener;
    }

    /**
     * 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 (internalOnAdapterChangeListener != null) {
            viewPager.removeOnAdapterChangeListener(internalOnAdapterChangeListener);
            internalOnAdapterChangeListener = null;
        }
        if (internalDataSetObserver != null) {
            viewPager.getAdapter().unregisterDataSetObserver(internalDataSetObserver);
            internalDataSetObserver = null;
        }
        if (internalOnTabSelectedListener != null) {
            tabLayout.removeOnTabSelectedListener(internalOnTabSelectedListener);
            internalOnTabSelectedListener = null;
        }
        if (internalTabLayoutOnPageChangeListener != null) {
            viewPager.removeOnPageChangeListener(internalTabLayoutOnPageChangeListener);
            internalTabLayoutOnPageChangeListener = null;
        }
        userOnTabSelectedListener = null;
        viewPager = null;
        tabLayout = null;
    }

    public void updateAllTabs() {
        int count = tabLayout.getTabCount();
        for (int i = 0; i < count; i++) {
            updateTab(tabLayout.getTabAt(i));
        }
    }

    /**
     * Override this method if you want to use custom tab layout.
     *
     * @param tabLayout TabLayout
     * @param adapter   PagerAdapter
     * @param position  Position of the item
     * @return TabLayout.Tab
     */
    private TabLayout.Tab onCreateTab(TabLayout tabLayout, PagerAdapter adapter, int position) {
        TabLayout.Tab tab = tabLayout.newTab();
        tab.setText(adapter.getPageTitle(position));
        return tab;
    }

    /**
     * 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 (setTabsFromPagerAdapterRunnable == null) {
            setTabsFromPagerAdapterRunnable = () -> setTabsFromPagerAdapter(tabLayout, viewPager.getAdapter(),
                    viewPager.getCurrentItem());
        }

        tabLayout.post(setTabsFromPagerAdapterRunnable);
    }

    private void handleOnTabSelected(TabLayout.Tab tab) {
        if (duringSetTabsFromPagerAdapter) {
            return;
        }
        viewPager.setCurrentItem(tab.getPosition());
        cancelPendingUpdateScrollPosition();

        if (userOnTabSelectedListener != null) {
            userOnTabSelectedListener.onTabSelected(tab);
        }
    }

    private void handleOnTabUnselected(TabLayout.Tab tab) {
        if (duringSetTabsFromPagerAdapter) {
            return;
        }
        if (userOnTabSelectedListener != null) {
            userOnTabSelectedListener.onTabUnselected(tab);
        }
    }

    private void handleOnTabReselected(TabLayout.Tab tab) {
        if (duringSetTabsFromPagerAdapter) {
            return;
        }
        if (userOnTabSelectedListener != null) {
            userOnTabSelectedListener.onTabReselected(tab);
        }
    }

    private void handleOnAdapterChanged(ViewPager viewPager, PagerAdapter oldAdapter, PagerAdapter newAdapter) {
        if (this.viewPager != viewPager) {
            return;
        }

        if (oldAdapter != null) {
            oldAdapter.unregisterDataSetObserver(internalDataSetObserver);
        }
        if (newAdapter != null) {
            newAdapter.registerDataSetObserver(internalDataSetObserver);
        }

        setTabsFromPagerAdapter(tabLayout, newAdapter, this.viewPager.getCurrentItem());
    }

    private void cancelPendingAdjustTabMode() {
        if (adjustTabModeRunnable != null) {
            tabLayout.removeCallbacks(adjustTabModeRunnable);
            adjustTabModeRunnable = null;
        }
    }

    private void cancelPendingSetTabsFromPagerAdapter() {
        if (setTabsFromPagerAdapterRunnable != null) {
            tabLayout.removeCallbacks(setTabsFromPagerAdapterRunnable);
            setTabsFromPagerAdapterRunnable = null;
        }
    }

    private void cancelPendingUpdateScrollPosition() {
        if (updateScrollPositionRunnable != null) {
            tabLayout.removeCallbacks(updateScrollPositionRunnable);
            updateScrollPositionRunnable = null;
        }
    }

    private void adjustTabMode(int prevScrollX) {
        if (adjustTabModeRunnable != null) {
            return;
        }

        if (prevScrollX < 0) {
            prevScrollX = tabLayout.getScrollX();
        }

        if (ViewCompat.isLaidOut(tabLayout)) {
            adjustTabModeInternal(tabLayout, prevScrollX);
        } else {
            final int prevScrollX1 = prevScrollX;
            adjustTabModeRunnable = () -> {
                adjustTabModeRunnable = null;
                adjustTabModeInternal(tabLayout, prevScrollX1);
            };
            tabLayout.post(adjustTabModeRunnable);
        }
    }

    private TabLayout.Tab createNewTab(TabLayout tabLayout, PagerAdapter adapter, int position) {
        return onCreateTab(tabLayout, adapter, position);
    }

    private void setupWithViewPager(@NonNull TabLayout tabLayout, @NonNull ViewPager viewPager) {
        final PagerAdapter adapter = viewPager.getAdapter();
        if (adapter == null) {
            throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
        }

        setTabsFromPagerAdapter(tabLayout, adapter, viewPager.getCurrentItem());

        viewPager.getAdapter().registerDataSetObserver(internalDataSetObserver);

        viewPager.addOnPageChangeListener(internalTabLayoutOnPageChangeListener);
        viewPager.addOnAdapterChangeListener(internalOnAdapterChangeListener);

        tabLayout.addOnTabSelectedListener(internalOnTabSelectedListener);
    }

    private void setTabsFromPagerAdapter(@NonNull TabLayout tabLayout, @Nullable PagerAdapter adapter,
            int currentItem) {
        try {
            duringSetTabsFromPagerAdapter = true;

            int prevSelectedTab = tabLayout.getSelectedTabPosition();
            int prevScrollX = tabLayout.getScrollX();

            // remove all tabs
            tabLayout.removeAllTabs();

            // add tabs
            if (adapter != null) {
                int count = adapter.getCount();
                for (int i = 0; i < count; i++) {
                    TabLayout.Tab tab = createNewTab(tabLayout, adapter, i);
                    tabLayout.addTab(tab, false);
                    updateTab(tab);
                }

                // select current tab
                currentItem = Math.min(currentItem, count - 1);
                if (currentItem >= 0) {
                    tabLayout.getTabAt(currentItem).select();
                }
            }

            // adjust tab mode & gravity
            if (autoAdjustTabMode) {
                adjustTabMode(prevScrollX);
            } else {
                // restore scroll position if needed
                int curTabMode = tabLayout.getTabMode();
                if (curTabMode == TabLayout.MODE_SCROLLABLE) {
                    tabLayout.scrollTo(prevScrollX, 0);
                }
            }
        } finally {
            duringSetTabsFromPagerAdapter = 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
                updateScrollPositionRunnable = () -> {
                    updateScrollPositionRunnable = null;
                    updateScrollPosition();
                };
                this.tabLayout.post(updateScrollPositionRunnable);
            }
        }
    }

    private void updateScrollPosition() {
        tabLayout.setScrollPosition(tabLayout.getSelectedTabPosition(), 0, false);
    }

    private static class FixedTabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
        private final WeakReference<TabLayout> mTabLayoutRef;
        private int mPreviousScrollState;
        private int mScrollState;

        public 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)
                final boolean updateText = (mScrollState == ViewPager.SCROLL_STATE_DRAGGING)
                        || (mScrollState == ViewPager.SCROLL_STATE_SETTLING
                                && mPreviousScrollState == ViewPager.SCROLL_STATE_DRAGGING);
                tabLayout.setScrollPosition(position, positionOffset, updateText);
            }
        }

        @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 == ViewPager.SCROLL_STATE_IDLE);
            }
        }

        private boolean shouldUpdateScrollPosition() {
            return (mScrollState == ViewPager.SCROLL_STATE_DRAGGING)
                    || ((mScrollState == ViewPager.SCROLL_STATE_SETTLING)
                            && (mPreviousScrollState == ViewPager.SCROLL_STATE_DRAGGING));
        }
    }

    static class Internal {
        private static final Method mMethodSelectTab;

        static {
            mMethodSelectTab = getAccessiblePrivateMethod(TabLayout.class, "selectTab", TabLayout.Tab.class,
                    boolean.class);
        }

        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);
            }
        }

        public static void selectTab(TabLayout tabLayout, TabLayout.Tab tab, boolean updateIndicator) {
            try {
                mMethodSelectTab.invoke(tabLayout, tab, updateIndicator);
            } catch (IllegalAccessException e) {
                Log.e("TabLayoutHelper", e.getMessage());
            } 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);
            }
        }
    }
}