Java tutorial
/* * 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); } } } }