diff --git a/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java b/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java deleted file mode 100644 index 24cbe89c9..000000000 --- a/app_pojavlauncher/src/main/java/android/support/design/widget/VerticalTabLayout.java +++ /dev/null @@ -1,2329 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * 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 android.support.design.widget; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_DRAGGING; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE; -import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_SETTLING; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Build; -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; -import androidx.annotation.IntDef; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.annotation.StringRes; -import com.google.android.material.R; - -import androidx.core.util.Pools; -import androidx.core.view.GravityCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.PagerAdapter; -import androidx.core.view.PointerIconCompat; -import androidx.core.view.ViewCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.core.widget.TextViewCompat; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.widget.TooltipCompat; -import android.text.Layout; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.SoundEffectConstants; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.*; -import androidx.core.app.*; - -import com.google.android.material.animation.AnimationUtils; -import com.google.android.material.tabs.TabItem; -import com.google.android.material.tabs.TabLayout; - -/** - * VerticalTabLayout provides a vertical layout to display tabs. - * - *
Population of the tabs to display is - * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can - * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)} - * respectively. To display the tab, you need to add it to the layout via one of the - * {@link #addTab(Tab)} methods. For example: - *
- * VerticalTabLayout tabLayout = ...;
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
- *
- * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
- * notified when any tab's selection state has been changed.
- *
- * You can also add items to VerticalTabLayout in your layout through the use of {@link TabItem}. - * An example usage is like so:
- * - *- * <android.support.design.widget.TabLayout - * android:layout_height="wrap_content" - * android:layout_width="match_parent"> - * - * <android.support.design.widget.TabItem - * android:text="@string/tab_text"/> - * - * <android.support.design.widget.TabItem - * android:icon="@drawable/ic_android"/> - * - * </android.support.design.widget.TabLayout> - *- * - *
- * If you're using a {@link ViewPager} together - * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together. - * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.
- * - *- * This view also supports being used as part of a ViewPager's decor, and can be added - * directly to the ViewPager in a layout resource file like so:
- * - *- * <android.support.v4.view.ViewPager - * android:layout_width="match_parent" - * android:layout_height="match_parent"> - * - * <android.support.design.widget.TabLayout - * android:layout_width="match_parent" - * android:layout_height="wrap_content" - * android:layout_gravity="top" /> - * - * </android.support.v4.view.ViewPager> - *- * - * @see Tabs - * - * @attr ref android.support.design.R.styleable#TabLayout_tabPadding - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom - * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart - * @attr ref android.support.design.R.styleable#TabLayout_tabBackground - * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance - */ -@ViewPager.DecorView -public class VerticalTabLayout extends LinearLayout { - - private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps - static final int DEFAULT_GAP_TEXT_ICON = 8; // dps - private static final int INVALID_WIDTH = -1; - private static final int DEFAULT_HEIGHT = 48; // dps - private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps - static final int FIXED_WRAP_GUTTER_MIN = 16; //dps - static final int MOTION_NON_ADJACENT_OFFSET = 24; - - private static final int ANIMATION_DURATION = 300; - - private static final Pools.Pool
- * Calling this method does not update the selected tab, it is only used for drawing purposes.
- *
- * @param position current scroll position
- * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
- * @param updateSelectedText Whether to update the text's selected state.
- */
- public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
- setScrollPosition(position, positionOffset, updateSelectedText, true);
- }
-
- void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
- boolean updateIndicatorPosition) {
- final int roundedPosition = Math.round(position + positionOffset);
- if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
- return;
- }
-
- // Set the indicator position, if enabled
- if (updateIndicatorPosition) {
- mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
- }
-
- // Now update the scroll position, canceling any running animation
- if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
- mScrollAnimator.cancel();
- }
- mTopScrollView.scrollTo(calculateScrollXForTab(position, positionOffset), 0);
-
- // Update the 'selected state' view as we scroll, if enabled
- if (updateSelectedText) {
- setSelectedTabView(roundedPosition);
- }
- }
-
- private float getScrollPosition() {
- return mTabStrip.getIndicatorPosition();
- }
-
- public void setLastTabAsBottom() {
- final int position = mTabs.size() - 1;
- final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
- final TabView view = (TabView) mTabStrip.getChildAt(position);
- view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- mTabStrip.removeViewAt(position);
- requestLayout();
-
- mTabs.remove(position);
- super.addView(view);
-
- final int newTabCount = mTabs.size();
- for (int i = position; i < newTabCount; i++) {
- mTabs.get(i).setPosition(i);
- }
-
- if (selectedTabPosition == position) {
- selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
- }
- }
-
- /**
- * Add a tab to this layout. The tab will be added at the end of the list.
- * If this is the first tab to be added it will become the selected tab.
- *
- * @param tab Tab to add
- */
- public void addTab(@NonNull Tab tab) {
- addTab(tab, mTabs.isEmpty());
- }
-
- /**
- * Add a tab to this layout. The tab will be inserted at position.
- * If this is the first tab to be added it will become the selected tab.
- *
- * @param tab The tab to add
- * @param position The new position of the tab
- */
- public void addTab(@NonNull Tab tab, int position) {
- addTab(tab, position, mTabs.isEmpty());
- }
-
- /**
- * Add a tab to this layout. The tab will be added at the end of the list.
- *
- * @param tab Tab to add
- * @param setSelected True if the added tab should become the selected tab.
- */
- public void addTab(@NonNull Tab tab, boolean setSelected) {
- addTab(tab, mTabs.size(), setSelected);
- }
-
- /**
- * Add a tab to this layout. The tab will be inserted at position.
- *
- * @param tab The tab to add
- * @param position The new position of the tab
- * @param setSelected True if the added tab should become the selected tab.
- */
- public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
- if (tab.mParent != this) {
- throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
- }
- configureTab(tab, position);
- addTabView(tab);
-
- if (setSelected) {
- tab.select();
- }
- }
-
- private void addTabFromItemView(@NonNull TabItem item) {
-
- final Tab tab = newTab();
- if (item.text != null) {
- tab.setText(item.text);
- }
- if (item.icon != null) {
- tab.setIcon(item.icon);
- }
- if (item.customLayout != 0) {
- tab.setCustomView(item.customLayout);
- }
- if (!TextUtils.isEmpty(item.getContentDescription())) {
- tab.setContentDescription(item.getContentDescription());
- }
- addTab(tab);
- }
-
- /**
- * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and
- * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
- */
- @Deprecated
- public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) {
- // The logic in this method emulates what we had before support for multiple
- // registered listeners.
- if (mSelectedListener != null) {
- removeOnTabSelectedListener(mSelectedListener);
- }
- // Update the deprecated field so that we can remove the passed listener the next
- // time we're called
- mSelectedListener = listener;
- if (listener != null) {
- addOnTabSelectedListener(listener);
- }
- }
-
- /**
- * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection
- * changes.
- *
- *
Components that add a listener should take care to remove it when finished via - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
- * - * @param listener listener to add - */ - public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - if (!mSelectedListeners.contains(listener)) { - mSelectedListeners.add(listener); - } - } - - /** - * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via - * {@link #addOnTabSelectedListener(OnTabSelectedListener)}. - * - * @param listener listener to remove - */ - public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - mSelectedListeners.remove(listener); - } - - /** - * Remove all previously added {@link TabLayout.OnTabSelectedListener}s. - */ - public void clearOnTabSelectedListeners() { - mSelectedListeners.clear(); - } - - /** - * Create and return a new {@link Tab}. You need to manually add this using - * {@link #addTab(Tab)} or a related method. - * - * @return A new Tab - * @see #addTab(Tab) - */ - @NonNull - public Tab newTab() { - Tab tab = sTabPool.acquire(); - if (tab == null) { - tab = new Tab(); - } - tab.mParent = this; - tab.mView = createTabView(tab); - return tab; - } - - /** - * Returns the number of tabs currently registered with the action bar. - * - * @return Tab count - */ - public int getTabCount() { - return mTabs.size(); - } - - /** - * Returns the tab at the specified index. - */ - @Nullable - public Tab getTabAt(int index) { - return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index); - } - - /** - * Returns the position of the current selected tab. - * - * @return selected tab position, or {@code -1} if there isn't a selected tab. - */ - public int getSelectedTabPosition() { - return mSelectedTab != null ? mSelectedTab.getPosition() : -1; - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param tab The tab to remove - */ - public void removeTab(Tab tab) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab does not belong to this TabLayout."); - } - - removeTabAt(tab.getPosition()); - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param position Position of the tab to remove - */ - public void removeTabAt(int position) { - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - removeTabViewAt(position); - - final Tab removedTab = mTabs.remove(position); - if (removedTab != null) { - removedTab.reset(); - sTabPool.release(removedTab); - } - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Remove all tabs from the action bar and deselect the current tab. - */ - public void removeAllTabs() { - // Remove all the views - for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) { - removeTabViewAt(i); - } - - for (final IteratorThis is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with - * auto-refresh enabled.
- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - */ - public void setupWithViewPager(@Nullable ViewPager viewPager) { - setupWithViewPager(viewPager, true); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - *This method will link the given ViewPager and this VerticalTabLayout together so that - * changes in one are automatically reflected in the other. This includes scroll state changes - * and clicks. The tabs displayed in this layout will be populated - * from the ViewPager adapter's page titles.
- * - *If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will - * trigger this layout to re-populate itself from the adapter's titles.
- * - *If the given ViewPager is non-null, it needs to already have a - * {@link PagerAdapter} set.
- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's - * content changes - */ - public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) { - setupWithViewPager(viewPager, autoRefresh, false); - } - - private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, - boolean implicitSetup) { - if (mViewPager != null) { - // If we've already been setup with a ViewPager, remove us from it - if (mPageChangeListener != null) { - mViewPager.removeOnPageChangeListener(mPageChangeListener); - } - if (mAdapterChangeListener != null) { - mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener); - } - } - - if (mCurrentVpSelectedListener != null) { - // If we already have a tab selected listener for the ViewPager, remove it - removeOnTabSelectedListener(mCurrentVpSelectedListener); - mCurrentVpSelectedListener = null; - } - - if (viewPager != null) { - mViewPager = viewPager; - - // Add our custom OnPageChangeListener to the ViewPager - if (mPageChangeListener == null) { - mPageChangeListener = new VerticalTabLayoutOnPageChangeListener(this); - } - mPageChangeListener.reset(); - viewPager.addOnPageChangeListener(mPageChangeListener); - - // Now we'll add a tab selected listener to set ViewPager's current item - mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); - addOnTabSelectedListener(mCurrentVpSelectedListener); - - final ViewPagerAdapter adapter = (VerticalTabLayout.ViewPagerAdapter) viewPager.getAdapter(); - if (adapter != null) { - // Now we'll populate ourselves from the pager adapter, adding an observer if - // autoRefresh is enabled - setPagerAdapter(adapter, autoRefresh); - } - - // Add a listener so that we're notified of any adapter changes - if (mAdapterChangeListener == null) { - mAdapterChangeListener = new AdapterChangeListener(); - } - mAdapterChangeListener.setAutoRefresh(autoRefresh); - viewPager.addOnAdapterChangeListener(mAdapterChangeListener); - - // Now update the scroll position to match the ViewPager's current item - setScrollPosition(viewPager.getCurrentItem(), 0f, true); - } else { - // We've been given a null ViewPager so we need to clear out the internal state, - // listeners and observers - mViewPager = null; - setPagerAdapter(null, false); - } - - mSetupViewPagerImplicitly = implicitSetup; - } - - /** - * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a VerticalTabLayout with a ViewPager - * together. When that method is used, the VerticalTabLayout will be automatically updated - * when the {@link PagerAdapter} is changed. - */ - @Deprecated - public void setTabsFromPagerAdapter(@Nullable final ViewPagerAdapter adapter) { - setPagerAdapter(adapter, false); - } - - @Override - public boolean shouldDelayChildPressedState() { - // Only delay the pressed state if the tabs can scroll - return getTabScrollRange() > 0; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager == null) { - // If we don't have a ViewPager already, check if our parent is a ViewPager to - // setup with it automatically - final ViewParent vp = getParent(); - if (vp instanceof ViewPager) { - // If we have a ViewPager parent and we've been added as part of its decor, let's - // assume that we should automatically setup to display any titles - setupWithViewPager((ViewPager) vp, true, true); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (mSetupViewPagerImplicitly) { - // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc - setupWithViewPager(null); - mSetupViewPagerImplicitly = false; - } - } - - private int getTabScrollRange() { - return Math.max(0, mTabStrip.getWidth() - mTopScrollView.getWidth() - mTopScrollView.getPaddingLeft() - - mTopScrollView.getPaddingRight()); - } - - void setPagerAdapter(@Nullable final ViewPagerAdapter adapter, final boolean addObserver) { - if (mPagerAdapter != null && mPagerAdapterObserver != null) { - // If we already have a PagerAdapter, unregister our observer - mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); - } - - mPagerAdapter = adapter; - - if (addObserver && adapter != null) { - // Register our observer on the new adapter - if (mPagerAdapterObserver == null) { - mPagerAdapterObserver = new PagerAdapterObserver(); - } - adapter.registerDataSetObserver(mPagerAdapterObserver); - } - - // Finally make sure we reflect the new adapter - populateFromPagerAdapter(); - } - - void populateFromPagerAdapter() { - removeAllTabs(); - - if (mPagerAdapter != null) { - final int adapterCount = mPagerAdapter.getCount(); - for (int i = 0; i < adapterCount; i++) { - addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); - if (mPagerAdapter.getIcon(i) != 0) { - getTabAt(i).setIcon(mPagerAdapter.getIcon(i)); - } - } - - // Make sure we reflect the currently set ViewPager item - if (mViewPager != null && adapterCount > 0) { - final int curItem = mViewPager.getCurrentItem(); - if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { - selectTab(getTabAt(curItem)); - } - } - } - } - - private void updateAllTabs() { - for (int i = 0, z = mTabs.size(); i < z; i++) { - mTabs.get(i).updateView(); - } - } - - private TabView createTabView(@NonNull final Tab tab) { - TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null; - if (tabView == null) { - tabView = new TabView(getContext()); - } - tabView.setTab(tab); - tabView.setFocusable(true); - tabView.setMinimumWidth(getTabMinWidth()); - return tabView; - } - - private void configureTab(Tab tab, int position) { - tab.setPosition(position); - mTabs.add(position, tab); - - final int count = mTabs.size(); - for (int i = position + 1; i < count; i++) { - mTabs.get(i).setPosition(i); - } - } - - private void addTabView(Tab tab) { - final TabView tabView = tab.mView; - mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs()); - } - - // @Override - public void addViewItem(View child) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - private void addViewInternal(final View child) { - if (child instanceof TabItem) { - addTabFromItemView((TabItem) child); - } else { - throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout"); - } - } - - private LinearLayout.LayoutParams createLayoutParamsForTabs() { - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - updateTabViewLayoutParams(lp); - return lp; - } - - private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { - lp.width = 0; - lp.weight = 1; - } else { - lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; - lp.weight = 0; - } - } - - int dpToPx(int dps) { - return Math.round(getResources().getDisplayMetrics().density * dps); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If we have a MeasureSpec which allows us to decide our height, try and use the default - // height - final int idealHeight = dpToPx(getDefaultHeight()) + mTopScrollView.getPaddingTop() + mTopScrollView.getPaddingBottom(); - - switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.AT_MOST: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)), - MeasureSpec.EXACTLY); - break; - case 0 /* MeasureSpec.UNSPECIFIED */: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY); - break; - } - - final int specWidth = MeasureSpec.getSize(widthMeasureSpec); - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { - // If we don't have an unspecified width spec, use the given size to calculate - // the max tab width - mTabMaxWidth = mRequestedTabMaxWidth > 0 - ? mRequestedTabMaxWidth - : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN); - } - - // Now super measure itself using the (possibly) modified height spec - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (getChildCount() == 1) { - // If we're in fixed mode then we need to make the tab strip is the same width as us - // so we don't scroll - final View child = getChildAt(0); - boolean remeasure = false; - - switch (mMode) { - case MODE_SCROLLABLE: - // We only need to resize the child if it's smaller than us. This is similar - // to fillViewport - remeasure = child.getMeasuredWidth() < mTopScrollView.getMeasuredWidth(); - break; - case MODE_FIXED: - // Resize the child so that it doesn't scroll - remeasure = child.getMeasuredWidth() != mTopScrollView.getMeasuredWidth(); - break; - } - - if (remeasure) { - // Re-measure the child with a widthSpec set to be exactly our measure width - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mTopScrollView.getPaddingTop() - + mTopScrollView.getPaddingBottom(), child.getLayoutParams().height); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - mTopScrollView.getMeasuredWidth(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } - - private void removeTabViewAt(int position) { - final TabView view = (TabView) mTabStrip.getChildAt(position); - mTabStrip.removeViewAt(position); - if (view != null) { - view.reset(); - mTabViewPool.release(view); - } - requestLayout(); - } - - private void animateToTab(int newPosition) { - if (newPosition == Tab.INVALID_POSITION) { - return; - } - - if (getWindowToken() == null || !ViewCompat.isLaidOut(this) - || mTabStrip.childrenNeedLayout()) { - // If we don't have a window token, or we haven't been laid out yet just draw the new - // position now - setScrollPosition(newPosition, 0f, true); - return; - } - - final int startScrollX = mTopScrollView.getScrollX(); - final int targetScrollX = calculateScrollXForTab(newPosition, 0); - - if (startScrollX != targetScrollX) { - ensureScrollAnimator(); - - mScrollAnimator.setIntValues(startScrollX, targetScrollX); - mScrollAnimator.start(); - } - - // Now animate the indicator - mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); - } - - private void ensureScrollAnimator() { - if (mScrollAnimator == null) { - mScrollAnimator = new ValueAnimator(); - mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - mScrollAnimator.setDuration(ANIMATION_DURATION); - mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - scrollTo((int) animator.getAnimatedValue(), 0); - } - }); - } - } - - void setScrollAnimatorListener(Animator.AnimatorListener listener) { - ensureScrollAnimator(); - mScrollAnimator.addListener(listener); - } - - private void setSelectedTabView(int position) { - final int tabCount = mTabStrip.getChildCount(); - if (position < tabCount) { - for (int i = 0; i < tabCount; i++) { - final View child = mTabStrip.getChildAt(i); - child.setSelected(i == position); - } - } - } - - void selectTab(Tab tab) { - selectTab(tab, true); - } - - void selectTab(final Tab tab, boolean updateIndicator) { - final Tab currentTab = mSelectedTab; - - if (currentTab == tab) { - if (currentTab != null) { - dispatchTabReselected(tab); - animateToTab(tab.getPosition()); - } - } else { - final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; - if (updateIndicator) { - if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION) - && newPosition != Tab.INVALID_POSITION) { - // If we don't currently have a tab, just draw the indicator - setScrollPosition(newPosition, 0f, true); - } else { - animateToTab(newPosition); - } - if (newPosition != Tab.INVALID_POSITION) { - setSelectedTabView(newPosition); - } - } - if (currentTab != null) { - dispatchTabUnselected(currentTab); - } - mSelectedTab = tab; - if (tab != null) { - dispatchTabSelected(tab); - } - } - } - - private void dispatchTabSelected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabSelected(tab); - } - } - - private void dispatchTabUnselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabUnselected(tab); - } - } - - private void dispatchTabReselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabReselected(tab); - } - } - - private int calculateScrollXForTab(int position, float positionOffset) { - if (mMode == MODE_SCROLLABLE) { - final View selectedChild = mTabStrip.getChildAt(position); - final View nextChild = position + 1 < mTabStrip.getChildCount() - ? mTabStrip.getChildAt(position + 1) - : null; - final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; - final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; - - // base scroll amount: places center of tab in center of parent - int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (mTopScrollView.getWidth() / 2); - // offset amount: fraction of the distance between centers of tabs - int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset); - - return (ViewCompat.getLayoutDirection(mTopScrollView) == ViewCompat.LAYOUT_DIRECTION_LTR) - ? scrollBase + scrollOffset - : scrollBase - scrollOffset; - } - return 0; - } - - private void applyModeAndGravity() { - int paddingStart = 0; - if (mMode == MODE_SCROLLABLE) { - // If we're scrollable, or fixed at start, inset using padding - paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); - } - ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); - - switch (mMode) { - case MODE_FIXED: - mTabStrip.setGravity(Gravity.CENTER_VERTICAL); - break; - case MODE_SCROLLABLE: - mTabStrip.setGravity(GravityCompat.START); - break; - } - - updateTabViews(true); - } - - void updateTabViews(final boolean requestLayout) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - View child = mTabStrip.getChildAt(i); - child.setMinimumWidth(getTabMinWidth()); - updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); - if (requestLayout) { - child.requestLayout(); - } - } - } - - /** - * A tab in this layout. Instances can be created via {@link #newTab()}. - */ - public static final class Tab { - - /** - * An invalid position for a tab. - * - * @see #getPosition() - */ - public static final int INVALID_POSITION = -1; - - private Object mTag; - private Drawable mIcon; - private CharSequence mText; - private CharSequence mContentDesc; - private int mPosition = INVALID_POSITION; - private View mCustomView; - - VerticalTabLayout mParent; - TabView mView; - - Tab() { - // Private constructor - } - - /** - * @return This Tab's tag object. - */ - @Nullable - public Object getTag() { - return mTag; - } - - /** - * Give this Tab an arbitrary object to hold for later use. - * - * @param tag Object to store - * @return The current instance for call chaining - */ - @NonNull - public Tab setTag(@Nullable Object tag) { - mTag = tag; - return this; - } - - - /** - * Returns the custom view used for this tab. - * - * @see #setCustomView(View) - * @see #setCustomView(int) - */ - @Nullable - public View getCustomView() { - return mCustomView; - } - - /** - * Set a custom view to be used for this tab. - *- * If the provided view contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *
- * - * @param view Custom view to be used as a tab. - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@Nullable View view) { - mCustomView = view; - updateView(); - return this; - } - - /** - * Set a custom view to be used for this tab. - *- * If the inflated layout contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *
- * - * @param resId A layout resource to inflate and use as a custom tab view - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@LayoutRes int resId) { - final LayoutInflater inflater = LayoutInflater.from(mView.getContext()); - return setCustomView(inflater.inflate(resId, mView, false)); - } - - /** - * Return the icon associated with this tab. - * - * @return The tab's icon - */ - @Nullable - public Drawable getIcon() { - return mIcon; - } - - /** - * Return the current position of this tab in the action bar. - * - * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in - * the action bar. - */ - public int getPosition() { - return mPosition; - } - - void setPosition(int position) { - mPosition = position; - } - - /** - * Return the text of this tab. - * - * @return The tab's text - */ - @Nullable - public CharSequence getText() { - return mText; - } - - /** - * Set the icon displayed on this tab. - * - * @param icon The drawable to use as an icon - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@Nullable Drawable icon) { - mIcon = icon; - updateView(); - return this; - } - - /** - * Set the icon displayed on this tab. - * - * @param resId A resource ID referring to the icon that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@DrawableRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setIcon(resId == 0 ? null : AppCompatResources.getDrawable(mParent.getContext(), resId)); - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param text The text to display - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@Nullable CharSequence text) { - mText = text; - updateView(); - return this; - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param resId A resource ID referring to the text that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setText(mParent.getResources().getText(resId)); - } - - /** - * Select this tab. Only valid if the tab has been added to the action bar. - */ - public void select() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - mParent.selectTab(this); - } - - /** - * Returns true if this tab is currently selected. - */ - public boolean isSelected() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return mParent.getSelectedTabPosition() == mPosition; - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param resId A resource ID referring to the description text - * @return The current instance for call chaining - * @see #setContentDescription(CharSequence) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setContentDescription(mParent.getResources().getText(resId)); - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param contentDesc Description of this tab's content - * @return The current instance for call chaining - * @see #setContentDescription(int) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@Nullable CharSequence contentDesc) { - mContentDesc = contentDesc; - updateView(); - return this; - } - - /** - * Gets a brief description of this tab's content for use in accessibility support. - * - * @return Description of this tab's content - * @see #setContentDescription(CharSequence) - * @see #setContentDescription(int) - */ - @Nullable - public CharSequence getContentDescription() { - return mContentDesc; - } - - void updateView() { - if (mView != null) { - mView.update(); - } - } - - void reset() { - mParent = null; - mView = null; - mTag = null; - mIcon = null; - mText = null; - mContentDesc = null; - mPosition = INVALID_POSITION; - mCustomView = null; - } - } - - class TabView extends LinearLayout { - private Tab mTab; - private TextView mTextView; - private ImageView mIconView; - - private View mCustomView; - private TextView mCustomTextView; - private ImageView mCustomIconView; - - private int mDefaultMaxLines = 2; - - public TabView(Context context) { - super(context); - if (mTabBackgroundResId != 0) { - ViewCompat.setBackground( - this, AppCompatResources.getDrawable(context, mTabBackgroundResId)); - } - ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, - mTabPaddingEnd, mTabPaddingBottom); - setGravity(Gravity.CENTER_VERTICAL); - setOrientation(HORIZONTAL); - setClickable(true); - ViewCompat.setPointerIcon(this, - PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); - } - - @Override - public boolean performClick() { - final boolean handled = super.performClick(); - - if (mTab != null) { - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); - } - mTab.select(); - return true; - } else { - return handled; - } - } - - @Override - public void setSelected(final boolean selected) { - final boolean changed = isSelected() != selected; - - super.setSelected(selected); - - if (changed && selected && Build.VERSION.SDK_INT < 16) { - // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - // Always dispatch this to the child views, regardless of whether the value has - // changed - if (mTextView != null) { - mTextView.setSelected(selected); - } - if (mIconView != null) { - mIconView.setSelected(selected); - } - if (mCustomView != null) { - mCustomView.setSelected(selected); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - // This view masquerades as an action bar tab. - event.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - // This view masquerades as an action bar tab. - info.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) { - final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec); - final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec); - final int maxWidth = getTabMaxWidth(); - - final int widthMeasureSpec; - final int heightMeasureSpec = origHeightMeasureSpec; - - if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED - || specWidthSize > maxWidth)) { - // If we have a max width and a given spec which is either unspecified or - // larger than the max width, update the width spec using the same mode - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST); - } else { - // Else, use the original width spec - widthMeasureSpec = origWidthMeasureSpec; - } - - // Now lets measure - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // We need to switch the text size based on whether the text is spanning 2 lines or not - if (mTextView != null) { - // final Resources res = getResources(); - float textSize = mTabTextSize; - int maxLines = mDefaultMaxLines; - - if (mIconView != null && mIconView.getVisibility() == VISIBLE) { - // If the icon view is being displayed, we limit the text to 1 line - maxLines = 2; - } else if (mTextView != null && mTextView.getLineCount() > 1) { - // Otherwise when we have text which wraps we reduce the text size - textSize = mTabTextMultiLineSize; - } - - final float curTextSize = mTextView.getTextSize(); - final int curLineCount = mTextView.getLineCount(); - final int curMaxLines = TextViewCompat.getMaxLines(mTextView); - - if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) { - // We've got a new text size and/or max lines... - boolean updateTextView = true; - - if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) { - // If we're in fixed mode, going up in text size and currently have 1 line - // then it's very easy to get into an infinite recursion. - // To combat that we check to see if the change in text size - // will cause a line count change. If so, abort the size change and stick - // to the smaller size. - final Layout layout = mTextView.getLayout(); - if (layout == null || approximateLineWidth(layout, 0, textSize) - > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) { - updateTextView = false; - } - } - - if (updateTextView) { - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - mTextView.setMaxLines(maxLines); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - - void setTab(@Nullable final Tab tab) { - if (tab != mTab) { - mTab = tab; - update(); - } - } - - void reset() { - setTab(null); - setSelected(false); - } - - final void update() { - final Tab tab = mTab; - final View custom = tab != null ? tab.getCustomView() : null; - if (custom != null) { - final ViewParent customParent = custom.getParent(); - if (customParent != this) { - if (customParent != null) { - ((ViewGroup) customParent).removeView(custom); - } - addView(custom); - } - mCustomView = custom; - if (mTextView != null) { - mTextView.setVisibility(GONE); - } - if (mIconView != null) { - mIconView.setVisibility(GONE); - mIconView.setImageDrawable(null); - } - - mCustomTextView = (TextView) custom.findViewById(android.R.id.text1); - if (mCustomTextView != null) { - mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView); - } - mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon); - } else { - // We do not have a custom view. Remove one if it already exists - if (mCustomView != null) { - removeView(mCustomView); - mCustomView = null; - } - mCustomTextView = null; - mCustomIconView = null; - } - - if (mCustomView == null) { - // If there isn't a custom view, we'll us our own in-built layouts - if (mIconView == null) { - ImageView iconView = (ImageView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_icon, this, false); - iconView.setLayoutParams(new LinearLayout.LayoutParams(dpToPx(25), - dpToPx(25))); - addView(iconView, 0); - mIconView = iconView; - } - if (mTextView == null) { - TextView textView = (TextView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_text, this, false); - LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - textParams.gravity = Gravity.CENTER_VERTICAL; - textView.setLayoutParams(textParams); - addView(textView); - mTextView = textView; - mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView); - } - TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance); - if (mTabTextColors != null) { - mTextView.setTextColor(mTabTextColors); - } - updateTextAndIcon(mTextView, mIconView); - } else { - // Else, we'll see if there is a TextView or ImageView present and update them - if (mCustomTextView != null || mCustomIconView != null) { - updateTextAndIcon(mCustomTextView, mCustomIconView); - } - } - - // Finally update our selected state - setSelected(tab != null && tab.isSelected()); - } - - private void updateTextAndIcon(@Nullable final TextView textView, - @Nullable final ImageView iconView) { - final Drawable icon = mTab != null ? mTab.getIcon() : null; - final CharSequence text = mTab != null ? mTab.getText() : null; - final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null; - - if (iconView != null) { - if (icon != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - iconView.setVisibility(GONE); - iconView.setImageDrawable(null); - } - iconView.setContentDescription(contentDesc); - } - - final boolean hasText = !TextUtils.isEmpty(text); - if (textView != null) { - if (hasText) { - textView.setText(text); - textView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - textView.setVisibility(GONE); - textView.setText(null); - } - textView.setContentDescription(contentDesc); - } - - if (iconView != null) { - MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams()); - int bottomMargin = 0; - if (hasText && iconView.getVisibility() == VISIBLE) { - // If we're showing both text and icon, add some margin bottom to the icon - bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON); - } - if (bottomMargin != lp.bottomMargin) { - lp.bottomMargin = bottomMargin; - iconView.requestLayout(); - } - } - TooltipCompat.setTooltipText(this, hasText ? null : contentDesc); - } - - public Tab getTab() { - return mTab; - } - - /** - * Approximates a given lines width with the new provided text size. - */ - private float approximateLineWidth(Layout layout, int line, float textSize) { - return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize()); - } - } - - private class SlidingTabStrip extends LinearLayout { - private int mSelectedIndicatorHeight; - private final Paint mSelectedIndicatorPaint; - - int mSelectedPosition = -1; - float mSelectionOffset; - - private int mLayoutDirection = -1; - - private int mIndicatorTop = -1; - private int mIndicatorBottom = -1; - - private ValueAnimator mIndicatorAnimator; - - SlidingTabStrip(Context context) { - super(context); - setWillNotDraw(false); - - - // Easy way to get vertical tab view - setOrientation(VERTICAL); - - mSelectedIndicatorPaint = new Paint(); - } - - void setSelectedIndicatorColor(int color) { - if (mSelectedIndicatorPaint.getColor() != color) { - mSelectedIndicatorPaint.setColor(color); - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void setSelectedIndicatorHeight(int height) { - if (mSelectedIndicatorHeight != height) { - mSelectedIndicatorHeight = height; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - boolean childrenNeedLayout() { - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - if (child.getWidth() <= 0) { - return true; - } - } - return false; - } - - void setIndicatorPositionFromTabPosition(int position, float positionOffset) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - mSelectedPosition = position; - mSelectionOffset = positionOffset; - updateIndicatorPosition(); - } - - float getIndicatorPosition() { - return mSelectedPosition + mSelectionOffset; - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - // Workaround for a bug before Android M where LinearLayout did not relayout itself when - // layout direction changed. - if (Build.VERSION.SDK_INT < 23 /* Build.VERSION_CODES.M */) { - //noinspection WrongConstant - if (mLayoutDirection != layoutDirection) { - requestLayout(); - mLayoutDirection = layoutDirection; - } - } - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { - // ScrollView will first measure use with UNSPECIFIED, and then with - // EXACTLY. Ignore the first call since anything we do will be overwritten anyway - return; - } - - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { - final int count = getChildCount(); - - // First we'll find the widest tab - int largestTabWidth = 0; - for (int i = 0, z = count; i < z; i++) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); - } - } - - if (largestTabWidth <= 0) { - // If we don't have a largest child yet, skip until the next measure pass - return; - } - - final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); - boolean remeasure = false; - - if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { - // If the tabs fit within our width minus gutters, we will set all tabs to have - // the same width - for (int i = 0; i < count; i++) { - final LinearLayout.LayoutParams lp = - (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.width != largestTabWidth || lp.weight != 0) { - lp.width = largestTabWidth; - lp.weight = 0; - remeasure = true; - } - } - } else { - // If the tabs will wrap to be larger than the width minus gutters, we need - // to switch to GRAVITY_FILL - mTabGravity = GRAVITY_FILL; - updateTabViews(false); - remeasure = true; - } - - if (remeasure) { - // Now re-measure after our changes - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - // If we're currently running an animation, lets cancel it and start a - // new animation with the remaining duration - mIndicatorAnimator.cancel(); - final long duration = mIndicatorAnimator.getDuration(); - animateIndicatorToPosition(mSelectedPosition, - Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); - } else { - // If we've been layed out, update the indicator position - updateIndicatorPosition(); - } - } - - private void updateIndicatorPosition() { - final View selectedTitle = getChildAt(mSelectedPosition); - int top, bottom; - - if (selectedTitle != null && selectedTitle.getWidth() > 0) { - top = selectedTitle.getTop(); - bottom = selectedTitle.getBottom(); - - if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - top = (int) (mSelectionOffset * nextTitle.getTop() + - (1.0f - mSelectionOffset) * top); - bottom = (int) (mSelectionOffset * nextTitle.getBottom() + - (1.0f - mSelectionOffset) * bottom); - } - } else { - top = bottom = -1; - } - - setIndicatorPosition(top, bottom); - } - - void setIndicatorPosition(int top, int bottom) { - if (top != mIndicatorTop || bottom != mIndicatorBottom) { - // If the indicator's left/right has changed, invalidate - mIndicatorTop = top; - mIndicatorBottom = bottom; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void animateIndicatorToPosition(final int position, int duration) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - final View targetView = getChildAt(position); - if (targetView == null) { - // If we don't have a view, just update the position now and return - updateIndicatorPosition(); - return; - } - - final int targetTop = targetView.getTop(); - final int targetBottom = targetView.getBottom(); - final int startTop; - final int startBottom; - - if (Math.abs(position - mSelectedPosition) <= 1) { - // If the views are adjacent, we'll animate from edge-to-edge - startTop = mIndicatorTop; - startBottom = mIndicatorBottom; - } else { - // Else, we'll just grow from the nearest edge - final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); - if (position < mSelectedPosition) { - // We're going end-to-start - /* - if (isRtl) { - startTop = startBottom = targetTop - offset; - } else { - */ - startTop = startBottom = targetBottom + offset; - // } - } else { - // We're going start-to-end - /* - if (isRtl) { - startLeft = startRight = targetRight + offset; - } else { - */ - startTop = startBottom = targetTop - offset; - // } - } - } - - if (startTop != targetTop || startBottom != targetBottom) { - ValueAnimator animator = mIndicatorAnimator = new ValueAnimator(); - animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(duration); - animator.setFloatValues(0, 1); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @SuppressLint("RestrictedApi") - @Override - public void onAnimationUpdate(ValueAnimator animator) { - final float fraction = animator.getAnimatedFraction(); - setIndicatorPosition( - AnimationUtils.lerp(startTop, targetTop, fraction), - AnimationUtils.lerp(startBottom, targetBottom, fraction)); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mSelectedPosition = position; - mSelectionOffset = 0f; - } - }); - animator.start(); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - // Thick colored line at the left side of the current selection - if (mIndicatorTop >= 0 && mIndicatorBottom > mIndicatorTop) { - canvas.drawRect(0, mIndicatorTop, - mSelectedIndicatorHeight, mIndicatorBottom, mSelectedIndicatorPaint); - } - } - } - - private static ColorStateList createColorStateList(int defaultColor, int selectedColor) { - final int[][] states = new int[2][]; - final int[] colors = new int[2]; - int i = 0; - - states[i] = SELECTED_STATE_SET; - colors[i] = selectedColor; - i++; - - // Default enabled state - states[i] = EMPTY_STATE_SET; - colors[i] = defaultColor; - i++; - - return new ColorStateList(states, colors); - } - - private int getDefaultHeight() { - boolean hasIconAndText = false; - for (int i = 0, count = mTabs.size(); i < count; i++) { - Tab tab = mTabs.get(i); - if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) { - hasIconAndText = true; - break; - } - } - return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT; - } - - private int getTabMinWidth() { - if (mRequestedTabMinWidth != INVALID_WIDTH) { - // If we have been given a min width, use it - return mRequestedTabMinWidth; - } - // Else, we'll use the default value - return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0; - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - // We don't care about the layout params of any views added to us, since we don't actually - // add them. The only view we add is the SlidingTabStrip, which is done manually. - // We return the default layout params so that we don't blow up if we're given a TabItem - // without android:layout_* values. - return generateDefaultLayoutParams(); - } - - int getTabMaxWidth() { - return mTabMaxWidth; - } - - /** - * A {@link ViewPager.OnPageChangeListener} class which contains the - * necessary calls back to the provided {@link TabLayout} so that the tab position is - * kept in sync. - * - *This class stores the provided VerticalTabLayout weakly, meaning that you can use
- * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
- * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
- * not cause a leak.
- */
- public static class VerticalTabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
- private final WeakReference