Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.design.widget;
     18 
     19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 import static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
     21 import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
     22 import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
     23 
     24 import android.animation.Animator;
     25 import android.animation.AnimatorListenerAdapter;
     26 import android.animation.ValueAnimator;
     27 import android.content.Context;
     28 import android.content.res.ColorStateList;
     29 import android.content.res.Resources;
     30 import android.content.res.TypedArray;
     31 import android.database.DataSetObserver;
     32 import android.graphics.Canvas;
     33 import android.graphics.Paint;
     34 import android.graphics.drawable.Drawable;
     35 import android.os.Build;
     36 import android.support.annotation.ColorInt;
     37 import android.support.annotation.DrawableRes;
     38 import android.support.annotation.IntDef;
     39 import android.support.annotation.LayoutRes;
     40 import android.support.annotation.NonNull;
     41 import android.support.annotation.Nullable;
     42 import android.support.annotation.RestrictTo;
     43 import android.support.annotation.StringRes;
     44 import android.support.design.R;
     45 import android.support.v4.util.Pools;
     46 import android.support.v4.view.GravityCompat;
     47 import android.support.v4.view.PagerAdapter;
     48 import android.support.v4.view.PointerIconCompat;
     49 import android.support.v4.view.ViewCompat;
     50 import android.support.v4.view.ViewPager;
     51 import android.support.v4.widget.TextViewCompat;
     52 import android.support.v7.app.ActionBar;
     53 import android.support.v7.content.res.AppCompatResources;
     54 import android.support.v7.widget.TooltipCompat;
     55 import android.text.Layout;
     56 import android.text.TextUtils;
     57 import android.util.AttributeSet;
     58 import android.util.TypedValue;
     59 import android.view.Gravity;
     60 import android.view.LayoutInflater;
     61 import android.view.SoundEffectConstants;
     62 import android.view.View;
     63 import android.view.ViewGroup;
     64 import android.view.ViewParent;
     65 import android.view.accessibility.AccessibilityEvent;
     66 import android.view.accessibility.AccessibilityNodeInfo;
     67 import android.widget.HorizontalScrollView;
     68 import android.widget.ImageView;
     69 import android.widget.LinearLayout;
     70 import android.widget.TextView;
     71 
     72 import java.lang.annotation.Retention;
     73 import java.lang.annotation.RetentionPolicy;
     74 import java.lang.ref.WeakReference;
     75 import java.util.ArrayList;
     76 import java.util.Iterator;
     77 
     78 /**
     79  * TabLayout provides a horizontal layout to display tabs.
     80  *
     81  * <p>Population of the tabs to display is
     82  * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can
     83  * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)}
     84  * respectively. To display the tab, you need to add it to the layout via one of the
     85  * {@link #addTab(Tab)} methods. For example:
     86  * <pre>
     87  * TabLayout tabLayout = ...;
     88  * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
     89  * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
     90  * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
     91  * </pre>
     92  * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
     93  * notified when any tab's selection state has been changed.
     94  *
     95  * <p>You can also add items to TabLayout in your layout through the use of {@link TabItem}.
     96  * An example usage is like so:</p>
     97  *
     98  * <pre>
     99  * &lt;android.support.design.widget.TabLayout
    100  *         android:layout_height=&quot;wrap_content&quot;
    101  *         android:layout_width=&quot;match_parent&quot;&gt;
    102  *
    103  *     &lt;android.support.design.widget.TabItem
    104  *             android:text=&quot;@string/tab_text&quot;/&gt;
    105  *
    106  *     &lt;android.support.design.widget.TabItem
    107  *             android:icon=&quot;@drawable/ic_android&quot;/&gt;
    108  *
    109  * &lt;/android.support.design.widget.TabLayout&gt;
    110  * </pre>
    111  *
    112  * <h3>ViewPager integration</h3>
    113  * <p>
    114  * If you're using a {@link android.support.v4.view.ViewPager} together
    115  * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together.
    116  * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p>
    117  *
    118  * <p>
    119  * This view also supports being used as part of a ViewPager's decor, and can be added
    120  * directly to the ViewPager in a layout resource file like so:</p>
    121  *
    122  * <pre>
    123  * &lt;android.support.v4.view.ViewPager
    124  *     android:layout_width=&quot;match_parent&quot;
    125  *     android:layout_height=&quot;match_parent&quot;&gt;
    126  *
    127  *     &lt;android.support.design.widget.TabLayout
    128  *         android:layout_width=&quot;match_parent&quot;
    129  *         android:layout_height=&quot;wrap_content&quot;
    130  *         android:layout_gravity=&quot;top&quot; /&gt;
    131  *
    132  * &lt;/android.support.v4.view.ViewPager&gt;
    133  * </pre>
    134  *
    135  * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
    136  *
    137  * @attr ref android.support.design.R.styleable#TabLayout_tabPadding
    138  * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart
    139  * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop
    140  * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd
    141  * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom
    142  * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart
    143  * @attr ref android.support.design.R.styleable#TabLayout_tabBackground
    144  * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth
    145  * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth
    146  * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance
    147  */
    148 @ViewPager.DecorView
    149 public class TabLayout extends HorizontalScrollView {
    150 
    151     private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps
    152     static final int DEFAULT_GAP_TEXT_ICON = 8; // dps
    153     private static final int INVALID_WIDTH = -1;
    154     private static final int DEFAULT_HEIGHT = 48; // dps
    155     private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
    156     static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
    157     static final int MOTION_NON_ADJACENT_OFFSET = 24;
    158 
    159     private static final int ANIMATION_DURATION = 300;
    160 
    161     private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16);
    162 
    163     /**
    164      * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab
    165      * labels and a larger number of tabs. They are best used for browsing contexts in touch
    166      * interfaces when users dont need to directly compare the tab labels.
    167      *
    168      * @see #setTabMode(int)
    169      * @see #getTabMode()
    170      */
    171     public static final int MODE_SCROLLABLE = 0;
    172 
    173     /**
    174      * Fixed tabs display all tabs concurrently and are best used with content that benefits from
    175      * quick pivots between tabs. The maximum number of tabs is limited by the views width.
    176      * Fixed tabs have equal width, based on the widest tab label.
    177      *
    178      * @see #setTabMode(int)
    179      * @see #getTabMode()
    180      */
    181     public static final int MODE_FIXED = 1;
    182 
    183     /**
    184      * @hide
    185      */
    186     @RestrictTo(LIBRARY_GROUP)
    187     @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED})
    188     @Retention(RetentionPolicy.SOURCE)
    189     public @interface Mode {}
    190 
    191     /**
    192      * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect
    193      * when used with {@link #MODE_FIXED}.
    194      *
    195      * @see #setTabGravity(int)
    196      * @see #getTabGravity()
    197      */
    198     public static final int GRAVITY_FILL = 0;
    199 
    200     /**
    201      * Gravity used to lay out the tabs in the center of the {@link TabLayout}.
    202      *
    203      * @see #setTabGravity(int)
    204      * @see #getTabGravity()
    205      */
    206     public static final int GRAVITY_CENTER = 1;
    207 
    208     /**
    209      * @hide
    210      */
    211     @RestrictTo(LIBRARY_GROUP)
    212     @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER})
    213     @Retention(RetentionPolicy.SOURCE)
    214     public @interface TabGravity {}
    215 
    216     /**
    217      * Callback interface invoked when a tab's selection state changes.
    218      */
    219     public interface OnTabSelectedListener {
    220 
    221         /**
    222          * Called when a tab enters the selected state.
    223          *
    224          * @param tab The tab that was selected
    225          */
    226         public void onTabSelected(Tab tab);
    227 
    228         /**
    229          * Called when a tab exits the selected state.
    230          *
    231          * @param tab The tab that was unselected
    232          */
    233         public void onTabUnselected(Tab tab);
    234 
    235         /**
    236          * Called when a tab that is already selected is chosen again by the user. Some applications
    237          * may use this action to return to the top level of a category.
    238          *
    239          * @param tab The tab that was reselected.
    240          */
    241         public void onTabReselected(Tab tab);
    242     }
    243 
    244     private final ArrayList<Tab> mTabs = new ArrayList<>();
    245     private Tab mSelectedTab;
    246 
    247     private final SlidingTabStrip mTabStrip;
    248 
    249     int mTabPaddingStart;
    250     int mTabPaddingTop;
    251     int mTabPaddingEnd;
    252     int mTabPaddingBottom;
    253 
    254     int mTabTextAppearance;
    255     ColorStateList mTabTextColors;
    256     float mTabTextSize;
    257     float mTabTextMultiLineSize;
    258 
    259     final int mTabBackgroundResId;
    260 
    261     int mTabMaxWidth = Integer.MAX_VALUE;
    262     private final int mRequestedTabMinWidth;
    263     private final int mRequestedTabMaxWidth;
    264     private final int mScrollableTabMinWidth;
    265 
    266     private int mContentInsetStart;
    267 
    268     int mTabGravity;
    269     int mMode;
    270 
    271     private OnTabSelectedListener mSelectedListener;
    272     private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
    273     private OnTabSelectedListener mCurrentVpSelectedListener;
    274 
    275     private ValueAnimator mScrollAnimator;
    276 
    277     ViewPager mViewPager;
    278     private PagerAdapter mPagerAdapter;
    279     private DataSetObserver mPagerAdapterObserver;
    280     private TabLayoutOnPageChangeListener mPageChangeListener;
    281     private AdapterChangeListener mAdapterChangeListener;
    282     private boolean mSetupViewPagerImplicitly;
    283 
    284     // Pool we use as a simple RecyclerBin
    285     private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12);
    286 
    287     public TabLayout(Context context) {
    288         this(context, null);
    289     }
    290 
    291     public TabLayout(Context context, AttributeSet attrs) {
    292         this(context, attrs, 0);
    293     }
    294 
    295     public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    296         super(context, attrs, defStyleAttr);
    297 
    298         ThemeUtils.checkAppCompatTheme(context);
    299 
    300         // Disable the Scroll Bar
    301         setHorizontalScrollBarEnabled(false);
    302 
    303         // Add the TabStrip
    304         mTabStrip = new SlidingTabStrip(context);
    305         super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
    306                 LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
    307 
    308         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
    309                 defStyleAttr, R.style.Widget_Design_TabLayout);
    310 
    311         mTabStrip.setSelectedIndicatorHeight(
    312                 a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
    313         mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
    314 
    315         mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
    316                 .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
    317         mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
    318                 mTabPaddingStart);
    319         mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
    320                 mTabPaddingTop);
    321         mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
    322                 mTabPaddingEnd);
    323         mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
    324                 mTabPaddingBottom);
    325 
    326         mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
    327                 R.style.TextAppearance_Design_Tab);
    328 
    329         // Text colors/sizes come from the text appearance first
    330         final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
    331                 android.support.v7.appcompat.R.styleable.TextAppearance);
    332         try {
    333             mTabTextSize = ta.getDimensionPixelSize(
    334                     android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
    335             mTabTextColors = ta.getColorStateList(
    336                     android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
    337         } finally {
    338             ta.recycle();
    339         }
    340 
    341         if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
    342             // If we have an explicit text color set, use it instead
    343             mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor);
    344         }
    345 
    346         if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
    347             // We have an explicit selected text color set, so we need to make merge it with the
    348             // current colors. This is exposed so that developers can use theme attributes to set
    349             // this (theme attrs in ColorStateLists are Lollipop+)
    350             final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
    351             mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
    352         }
    353 
    354         mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,
    355                 INVALID_WIDTH);
    356         mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,
    357                 INVALID_WIDTH);
    358         mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
    359         mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
    360         mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
    361         mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
    362         a.recycle();
    363 
    364         // TODO add attr for these
    365         final Resources res = getResources();
    366         mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
    367         mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
    368 
    369         // Now apply the tab mode and gravity
    370         applyModeAndGravity();
    371     }
    372 
    373     /**
    374      * Sets the tab indicator's color for the currently selected tab.
    375      *
    376      * @param color color to use for the indicator
    377      *
    378      * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor
    379      */
    380     public void setSelectedTabIndicatorColor(@ColorInt int color) {
    381         mTabStrip.setSelectedIndicatorColor(color);
    382     }
    383 
    384     /**
    385      * Sets the tab indicator's height for the currently selected tab.
    386      *
    387      * @param height height to use for the indicator in pixels
    388      *
    389      * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight
    390      */
    391     public void setSelectedTabIndicatorHeight(int height) {
    392         mTabStrip.setSelectedIndicatorHeight(height);
    393     }
    394 
    395     /**
    396      * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
    397      * part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
    398      * <p>
    399      * Calling this method does not update the selected tab, it is only used for drawing purposes.
    400      *
    401      * @param position current scroll position
    402      * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
    403      * @param updateSelectedText Whether to update the text's selected state.
    404      */
    405     public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
    406         setScrollPosition(position, positionOffset, updateSelectedText, true);
    407     }
    408 
    409     void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
    410             boolean updateIndicatorPosition) {
    411         final int roundedPosition = Math.round(position + positionOffset);
    412         if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
    413             return;
    414         }
    415 
    416         // Set the indicator position, if enabled
    417         if (updateIndicatorPosition) {
    418             mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
    419         }
    420 
    421         // Now update the scroll position, canceling any running animation
    422         if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
    423             mScrollAnimator.cancel();
    424         }
    425         scrollTo(calculateScrollXForTab(position, positionOffset), 0);
    426 
    427         // Update the 'selected state' view as we scroll, if enabled
    428         if (updateSelectedText) {
    429             setSelectedTabView(roundedPosition);
    430         }
    431     }
    432 
    433     private float getScrollPosition() {
    434         return mTabStrip.getIndicatorPosition();
    435     }
    436 
    437     /**
    438      * Add a tab to this layout. The tab will be added at the end of the list.
    439      * If this is the first tab to be added it will become the selected tab.
    440      *
    441      * @param tab Tab to add
    442      */
    443     public void addTab(@NonNull Tab tab) {
    444         addTab(tab, mTabs.isEmpty());
    445     }
    446 
    447     /**
    448      * Add a tab to this layout. The tab will be inserted at <code>position</code>.
    449      * If this is the first tab to be added it will become the selected tab.
    450      *
    451      * @param tab The tab to add
    452      * @param position The new position of the tab
    453      */
    454     public void addTab(@NonNull Tab tab, int position) {
    455         addTab(tab, position, mTabs.isEmpty());
    456     }
    457 
    458     /**
    459      * Add a tab to this layout. The tab will be added at the end of the list.
    460      *
    461      * @param tab Tab to add
    462      * @param setSelected True if the added tab should become the selected tab.
    463      */
    464     public void addTab(@NonNull Tab tab, boolean setSelected) {
    465         addTab(tab, mTabs.size(), setSelected);
    466     }
    467 
    468     /**
    469      * Add a tab to this layout. The tab will be inserted at <code>position</code>.
    470      *
    471      * @param tab The tab to add
    472      * @param position The new position of the tab
    473      * @param setSelected True if the added tab should become the selected tab.
    474      */
    475     public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
    476         if (tab.mParent != this) {
    477             throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    478         }
    479         configureTab(tab, position);
    480         addTabView(tab);
    481 
    482         if (setSelected) {
    483             tab.select();
    484         }
    485     }
    486 
    487     private void addTabFromItemView(@NonNull TabItem item) {
    488         final Tab tab = newTab();
    489         if (item.mText != null) {
    490             tab.setText(item.mText);
    491         }
    492         if (item.mIcon != null) {
    493             tab.setIcon(item.mIcon);
    494         }
    495         if (item.mCustomLayout != 0) {
    496             tab.setCustomView(item.mCustomLayout);
    497         }
    498         if (!TextUtils.isEmpty(item.getContentDescription())) {
    499             tab.setContentDescription(item.getContentDescription());
    500         }
    501         addTab(tab);
    502     }
    503 
    504     /**
    505      * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and
    506      * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
    507      */
    508     @Deprecated
    509     public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) {
    510         // The logic in this method emulates what we had before support for multiple
    511         // registered listeners.
    512         if (mSelectedListener != null) {
    513             removeOnTabSelectedListener(mSelectedListener);
    514         }
    515         // Update the deprecated field so that we can remove the passed listener the next
    516         // time we're called
    517         mSelectedListener = listener;
    518         if (listener != null) {
    519             addOnTabSelectedListener(listener);
    520         }
    521     }
    522 
    523     /**
    524      * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection
    525      * changes.
    526      *
    527      * <p>Components that add a listener should take care to remove it when finished via
    528      * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p>
    529      *
    530      * @param listener listener to add
    531      */
    532     public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
    533         if (!mSelectedListeners.contains(listener)) {
    534             mSelectedListeners.add(listener);
    535         }
    536     }
    537 
    538     /**
    539      * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via
    540      * {@link #addOnTabSelectedListener(OnTabSelectedListener)}.
    541      *
    542      * @param listener listener to remove
    543      */
    544     public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
    545         mSelectedListeners.remove(listener);
    546     }
    547 
    548     /**
    549      * Remove all previously added {@link TabLayout.OnTabSelectedListener}s.
    550      */
    551     public void clearOnTabSelectedListeners() {
    552         mSelectedListeners.clear();
    553     }
    554 
    555     /**
    556      * Create and return a new {@link Tab}. You need to manually add this using
    557      * {@link #addTab(Tab)} or a related method.
    558      *
    559      * @return A new Tab
    560      * @see #addTab(Tab)
    561      */
    562     @NonNull
    563     public Tab newTab() {
    564         Tab tab = sTabPool.acquire();
    565         if (tab == null) {
    566             tab = new Tab();
    567         }
    568         tab.mParent = this;
    569         tab.mView = createTabView(tab);
    570         return tab;
    571     }
    572 
    573     /**
    574      * Returns the number of tabs currently registered with the action bar.
    575      *
    576      * @return Tab count
    577      */
    578     public int getTabCount() {
    579         return mTabs.size();
    580     }
    581 
    582     /**
    583      * Returns the tab at the specified index.
    584      */
    585     @Nullable
    586     public Tab getTabAt(int index) {
    587         return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index);
    588     }
    589 
    590     /**
    591      * Returns the position of the current selected tab.
    592      *
    593      * @return selected tab position, or {@code -1} if there isn't a selected tab.
    594      */
    595     public int getSelectedTabPosition() {
    596         return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
    597     }
    598 
    599     /**
    600      * Remove a tab from the layout. If the removed tab was selected it will be deselected
    601      * and another tab will be selected if present.
    602      *
    603      * @param tab The tab to remove
    604      */
    605     public void removeTab(Tab tab) {
    606         if (tab.mParent != this) {
    607             throw new IllegalArgumentException("Tab does not belong to this TabLayout.");
    608         }
    609 
    610         removeTabAt(tab.getPosition());
    611     }
    612 
    613     /**
    614      * Remove a tab from the layout. If the removed tab was selected it will be deselected
    615      * and another tab will be selected if present.
    616      *
    617      * @param position Position of the tab to remove
    618      */
    619     public void removeTabAt(int position) {
    620         final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
    621         removeTabViewAt(position);
    622 
    623         final Tab removedTab = mTabs.remove(position);
    624         if (removedTab != null) {
    625             removedTab.reset();
    626             sTabPool.release(removedTab);
    627         }
    628 
    629         final int newTabCount = mTabs.size();
    630         for (int i = position; i < newTabCount; i++) {
    631             mTabs.get(i).setPosition(i);
    632         }
    633 
    634         if (selectedTabPosition == position) {
    635             selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
    636         }
    637     }
    638 
    639     /**
    640      * Remove all tabs from the action bar and deselect the current tab.
    641      */
    642     public void removeAllTabs() {
    643         // Remove all the views
    644         for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) {
    645             removeTabViewAt(i);
    646         }
    647 
    648         for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) {
    649             final Tab tab = i.next();
    650             i.remove();
    651             tab.reset();
    652             sTabPool.release(tab);
    653         }
    654 
    655         mSelectedTab = null;
    656     }
    657 
    658     /**
    659      * Set the behavior mode for the Tabs in this layout. The valid input options are:
    660      * <ul>
    661      * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used
    662      * with content that benefits from quick pivots between tabs.</li>
    663      * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
    664      * and can contain longer tab labels and a larger number of tabs. They are best used for
    665      * browsing contexts in touch interfaces when users dont need to directly compare the tab
    666      * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li>
    667      * </ul>
    668      *
    669      * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
    670      *
    671      * @attr ref android.support.design.R.styleable#TabLayout_tabMode
    672      */
    673     public void setTabMode(@Mode int mode) {
    674         if (mode != mMode) {
    675             mMode = mode;
    676             applyModeAndGravity();
    677         }
    678     }
    679 
    680     /**
    681      * Returns the current mode used by this {@link TabLayout}.
    682      *
    683      * @see #setTabMode(int)
    684      */
    685     @Mode
    686     public int getTabMode() {
    687         return mMode;
    688     }
    689 
    690     /**
    691      * Set the gravity to use when laying out the tabs.
    692      *
    693      * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
    694      *
    695      * @attr ref android.support.design.R.styleable#TabLayout_tabGravity
    696      */
    697     public void setTabGravity(@TabGravity int gravity) {
    698         if (mTabGravity != gravity) {
    699             mTabGravity = gravity;
    700             applyModeAndGravity();
    701         }
    702     }
    703 
    704     /**
    705      * The current gravity used for laying out tabs.
    706      *
    707      * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
    708      */
    709     @TabGravity
    710     public int getTabGravity() {
    711         return mTabGravity;
    712     }
    713 
    714     /**
    715      * Sets the text colors for the different states (normal, selected) used for the tabs.
    716      *
    717      * @see #getTabTextColors()
    718      */
    719     public void setTabTextColors(@Nullable ColorStateList textColor) {
    720         if (mTabTextColors != textColor) {
    721             mTabTextColors = textColor;
    722             updateAllTabs();
    723         }
    724     }
    725 
    726     /**
    727      * Gets the text colors for the different states (normal, selected) used for the tabs.
    728      */
    729     @Nullable
    730     public ColorStateList getTabTextColors() {
    731         return mTabTextColors;
    732     }
    733 
    734     /**
    735      * Sets the text colors for the different states (normal, selected) used for the tabs.
    736      *
    737      * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor
    738      * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor
    739      */
    740     public void setTabTextColors(int normalColor, int selectedColor) {
    741         setTabTextColors(createColorStateList(normalColor, selectedColor));
    742     }
    743 
    744     /**
    745      * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
    746      *
    747      * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with
    748      * auto-refresh enabled.</p>
    749      *
    750      * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
    751      */
    752     public void setupWithViewPager(@Nullable ViewPager viewPager) {
    753         setupWithViewPager(viewPager, true);
    754     }
    755 
    756     /**
    757      * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
    758      *
    759      * <p>This method will link the given ViewPager and this TabLayout together so that
    760      * changes in one are automatically reflected in the other. This includes scroll state changes
    761      * and clicks. The tabs displayed in this layout will be populated
    762      * from the ViewPager adapter's page titles.</p>
    763      *
    764      * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will
    765      * trigger this layout to re-populate itself from the adapter's titles.</p>
    766      *
    767      * <p>If the given ViewPager is non-null, it needs to already have a
    768      * {@link PagerAdapter} set.</p>
    769      *
    770      * @param viewPager   the ViewPager to link to, or {@code null} to clear any previous link
    771      * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's
    772      *                    content changes
    773      */
    774     public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
    775         setupWithViewPager(viewPager, autoRefresh, false);
    776     }
    777 
    778     private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
    779             boolean implicitSetup) {
    780         if (mViewPager != null) {
    781             // If we've already been setup with a ViewPager, remove us from it
    782             if (mPageChangeListener != null) {
    783                 mViewPager.removeOnPageChangeListener(mPageChangeListener);
    784             }
    785             if (mAdapterChangeListener != null) {
    786                 mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
    787             }
    788         }
    789 
    790         if (mCurrentVpSelectedListener != null) {
    791             // If we already have a tab selected listener for the ViewPager, remove it
    792             removeOnTabSelectedListener(mCurrentVpSelectedListener);
    793             mCurrentVpSelectedListener = null;
    794         }
    795 
    796         if (viewPager != null) {
    797             mViewPager = viewPager;
    798 
    799             // Add our custom OnPageChangeListener to the ViewPager
    800             if (mPageChangeListener == null) {
    801                 mPageChangeListener = new TabLayoutOnPageChangeListener(this);
    802             }
    803             mPageChangeListener.reset();
    804             viewPager.addOnPageChangeListener(mPageChangeListener);
    805 
    806             // Now we'll add a tab selected listener to set ViewPager's current item
    807             mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
    808             addOnTabSelectedListener(mCurrentVpSelectedListener);
    809 
    810             final PagerAdapter adapter = viewPager.getAdapter();
    811             if (adapter != null) {
    812                 // Now we'll populate ourselves from the pager adapter, adding an observer if
    813                 // autoRefresh is enabled
    814                 setPagerAdapter(adapter, autoRefresh);
    815             }
    816 
    817             // Add a listener so that we're notified of any adapter changes
    818             if (mAdapterChangeListener == null) {
    819                 mAdapterChangeListener = new AdapterChangeListener();
    820             }
    821             mAdapterChangeListener.setAutoRefresh(autoRefresh);
    822             viewPager.addOnAdapterChangeListener(mAdapterChangeListener);
    823 
    824             // Now update the scroll position to match the ViewPager's current item
    825             setScrollPosition(viewPager.getCurrentItem(), 0f, true);
    826         } else {
    827             // We've been given a null ViewPager so we need to clear out the internal state,
    828             // listeners and observers
    829             mViewPager = null;
    830             setPagerAdapter(null, false);
    831         }
    832 
    833         mSetupViewPagerImplicitly = implicitSetup;
    834     }
    835 
    836     /**
    837      * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a TabLayout with a ViewPager
    838      *             together. When that method is used, the TabLayout will be automatically updated
    839      *             when the {@link PagerAdapter} is changed.
    840      */
    841     @Deprecated
    842     public void setTabsFromPagerAdapter(@Nullable final PagerAdapter adapter) {
    843         setPagerAdapter(adapter, false);
    844     }
    845 
    846     @Override
    847     public boolean shouldDelayChildPressedState() {
    848         // Only delay the pressed state if the tabs can scroll
    849         return getTabScrollRange() > 0;
    850     }
    851 
    852     @Override
    853     protected void onAttachedToWindow() {
    854         super.onAttachedToWindow();
    855 
    856         if (mViewPager == null) {
    857             // If we don't have a ViewPager already, check if our parent is a ViewPager to
    858             // setup with it automatically
    859             final ViewParent vp = getParent();
    860             if (vp instanceof ViewPager) {
    861                 // If we have a ViewPager parent and we've been added as part of its decor, let's
    862                 // assume that we should automatically setup to display any titles
    863                 setupWithViewPager((ViewPager) vp, true, true);
    864             }
    865         }
    866     }
    867 
    868     @Override
    869     protected void onDetachedFromWindow() {
    870         super.onDetachedFromWindow();
    871 
    872         if (mSetupViewPagerImplicitly) {
    873             // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc
    874             setupWithViewPager(null);
    875             mSetupViewPagerImplicitly = false;
    876         }
    877     }
    878 
    879     private int getTabScrollRange() {
    880         return Math.max(0, mTabStrip.getWidth() - getWidth() - getPaddingLeft()
    881                 - getPaddingRight());
    882     }
    883 
    884     void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
    885         if (mPagerAdapter != null && mPagerAdapterObserver != null) {
    886             // If we already have a PagerAdapter, unregister our observer
    887             mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
    888         }
    889 
    890         mPagerAdapter = adapter;
    891 
    892         if (addObserver && adapter != null) {
    893             // Register our observer on the new adapter
    894             if (mPagerAdapterObserver == null) {
    895                 mPagerAdapterObserver = new PagerAdapterObserver();
    896             }
    897             adapter.registerDataSetObserver(mPagerAdapterObserver);
    898         }
    899 
    900         // Finally make sure we reflect the new adapter
    901         populateFromPagerAdapter();
    902     }
    903 
    904     void populateFromPagerAdapter() {
    905         removeAllTabs();
    906 
    907         if (mPagerAdapter != null) {
    908             final int adapterCount = mPagerAdapter.getCount();
    909             for (int i = 0; i < adapterCount; i++) {
    910                 addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
    911             }
    912 
    913             // Make sure we reflect the currently set ViewPager item
    914             if (mViewPager != null && adapterCount > 0) {
    915                 final int curItem = mViewPager.getCurrentItem();
    916                 if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
    917                     selectTab(getTabAt(curItem));
    918                 }
    919             }
    920         }
    921     }
    922 
    923     private void updateAllTabs() {
    924         for (int i = 0, z = mTabs.size(); i < z; i++) {
    925             mTabs.get(i).updateView();
    926         }
    927     }
    928 
    929     private TabView createTabView(@NonNull final Tab tab) {
    930         TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
    931         if (tabView == null) {
    932             tabView = new TabView(getContext());
    933         }
    934         tabView.setTab(tab);
    935         tabView.setFocusable(true);
    936         tabView.setMinimumWidth(getTabMinWidth());
    937         return tabView;
    938     }
    939 
    940     private void configureTab(Tab tab, int position) {
    941         tab.setPosition(position);
    942         mTabs.add(position, tab);
    943 
    944         final int count = mTabs.size();
    945         for (int i = position + 1; i < count; i++) {
    946             mTabs.get(i).setPosition(i);
    947         }
    948     }
    949 
    950     private void addTabView(Tab tab) {
    951         final TabView tabView = tab.mView;
    952         mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
    953     }
    954 
    955     @Override
    956     public void addView(View child) {
    957         addViewInternal(child);
    958     }
    959 
    960     @Override
    961     public void addView(View child, int index) {
    962         addViewInternal(child);
    963     }
    964 
    965     @Override
    966     public void addView(View child, ViewGroup.LayoutParams params) {
    967         addViewInternal(child);
    968     }
    969 
    970     @Override
    971     public void addView(View child, int index, ViewGroup.LayoutParams params) {
    972         addViewInternal(child);
    973     }
    974 
    975     private void addViewInternal(final View child) {
    976         if (child instanceof TabItem) {
    977             addTabFromItemView((TabItem) child);
    978         } else {
    979             throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout");
    980         }
    981     }
    982 
    983     private LinearLayout.LayoutParams createLayoutParamsForTabs() {
    984         final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
    985                 LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
    986         updateTabViewLayoutParams(lp);
    987         return lp;
    988     }
    989 
    990     private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
    991         if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
    992             lp.width = 0;
    993             lp.weight = 1;
    994         } else {
    995             lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
    996             lp.weight = 0;
    997         }
    998     }
    999 
   1000     int dpToPx(int dps) {
   1001         return Math.round(getResources().getDisplayMetrics().density * dps);
   1002     }
   1003 
   1004     @Override
   1005     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1006         // If we have a MeasureSpec which allows us to decide our height, try and use the default
   1007         // height
   1008         final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
   1009         switch (MeasureSpec.getMode(heightMeasureSpec)) {
   1010             case MeasureSpec.AT_MOST:
   1011                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
   1012                         Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
   1013                         MeasureSpec.EXACTLY);
   1014                 break;
   1015             case MeasureSpec.UNSPECIFIED:
   1016                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
   1017                 break;
   1018         }
   1019 
   1020         final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
   1021         if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
   1022             // If we don't have an unspecified width spec, use the given size to calculate
   1023             // the max tab width
   1024             mTabMaxWidth = mRequestedTabMaxWidth > 0
   1025                     ? mRequestedTabMaxWidth
   1026                     : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
   1027         }
   1028 
   1029         // Now super measure itself using the (possibly) modified height spec
   1030         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1031 
   1032         if (getChildCount() == 1) {
   1033             // If we're in fixed mode then we need to make the tab strip is the same width as us
   1034             // so we don't scroll
   1035             final View child = getChildAt(0);
   1036             boolean remeasure = false;
   1037 
   1038             switch (mMode) {
   1039                 case MODE_SCROLLABLE:
   1040                     // We only need to resize the child if it's smaller than us. This is similar
   1041                     // to fillViewport
   1042                     remeasure = child.getMeasuredWidth() < getMeasuredWidth();
   1043                     break;
   1044                 case MODE_FIXED:
   1045                     // Resize the child so that it doesn't scroll
   1046                     remeasure = child.getMeasuredWidth() != getMeasuredWidth();
   1047                     break;
   1048             }
   1049 
   1050             if (remeasure) {
   1051                 // Re-measure the child with a widthSpec set to be exactly our measure width
   1052                 int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
   1053                         + getPaddingBottom(), child.getLayoutParams().height);
   1054                 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
   1055                         getMeasuredWidth(), MeasureSpec.EXACTLY);
   1056                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   1057             }
   1058         }
   1059     }
   1060 
   1061     private void removeTabViewAt(int position) {
   1062         final TabView view = (TabView) mTabStrip.getChildAt(position);
   1063         mTabStrip.removeViewAt(position);
   1064         if (view != null) {
   1065             view.reset();
   1066             mTabViewPool.release(view);
   1067         }
   1068         requestLayout();
   1069     }
   1070 
   1071     private void animateToTab(int newPosition) {
   1072         if (newPosition == Tab.INVALID_POSITION) {
   1073             return;
   1074         }
   1075 
   1076         if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
   1077                 || mTabStrip.childrenNeedLayout()) {
   1078             // If we don't have a window token, or we haven't been laid out yet just draw the new
   1079             // position now
   1080             setScrollPosition(newPosition, 0f, true);
   1081             return;
   1082         }
   1083 
   1084         final int startScrollX = getScrollX();
   1085         final int targetScrollX = calculateScrollXForTab(newPosition, 0);
   1086 
   1087         if (startScrollX != targetScrollX) {
   1088             ensureScrollAnimator();
   1089 
   1090             mScrollAnimator.setIntValues(startScrollX, targetScrollX);
   1091             mScrollAnimator.start();
   1092         }
   1093 
   1094         // Now animate the indicator
   1095         mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
   1096     }
   1097 
   1098     private void ensureScrollAnimator() {
   1099         if (mScrollAnimator == null) {
   1100             mScrollAnimator = new ValueAnimator();
   1101             mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
   1102             mScrollAnimator.setDuration(ANIMATION_DURATION);
   1103             mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   1104                 @Override
   1105                 public void onAnimationUpdate(ValueAnimator animator) {
   1106                     scrollTo((int) animator.getAnimatedValue(), 0);
   1107                 }
   1108             });
   1109         }
   1110     }
   1111 
   1112     void setScrollAnimatorListener(Animator.AnimatorListener listener) {
   1113         ensureScrollAnimator();
   1114         mScrollAnimator.addListener(listener);
   1115     }
   1116 
   1117     private void setSelectedTabView(int position) {
   1118         final int tabCount = mTabStrip.getChildCount();
   1119         if (position < tabCount) {
   1120             for (int i = 0; i < tabCount; i++) {
   1121                 final View child = mTabStrip.getChildAt(i);
   1122                 child.setSelected(i == position);
   1123             }
   1124         }
   1125     }
   1126 
   1127     void selectTab(Tab tab) {
   1128         selectTab(tab, true);
   1129     }
   1130 
   1131     void selectTab(final Tab tab, boolean updateIndicator) {
   1132         final Tab currentTab = mSelectedTab;
   1133 
   1134         if (currentTab == tab) {
   1135             if (currentTab != null) {
   1136                 dispatchTabReselected(tab);
   1137                 animateToTab(tab.getPosition());
   1138             }
   1139         } else {
   1140             final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
   1141             if (updateIndicator) {
   1142                 if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION)
   1143                         && newPosition != Tab.INVALID_POSITION) {
   1144                     // If we don't currently have a tab, just draw the indicator
   1145                     setScrollPosition(newPosition, 0f, true);
   1146                 } else {
   1147                     animateToTab(newPosition);
   1148                 }
   1149                 if (newPosition != Tab.INVALID_POSITION) {
   1150                     setSelectedTabView(newPosition);
   1151                 }
   1152             }
   1153             if (currentTab != null) {
   1154                 dispatchTabUnselected(currentTab);
   1155             }
   1156             mSelectedTab = tab;
   1157             if (tab != null) {
   1158                 dispatchTabSelected(tab);
   1159             }
   1160         }
   1161     }
   1162 
   1163     private void dispatchTabSelected(@NonNull final Tab tab) {
   1164         for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
   1165             mSelectedListeners.get(i).onTabSelected(tab);
   1166         }
   1167     }
   1168 
   1169     private void dispatchTabUnselected(@NonNull final Tab tab) {
   1170         for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
   1171             mSelectedListeners.get(i).onTabUnselected(tab);
   1172         }
   1173     }
   1174 
   1175     private void dispatchTabReselected(@NonNull final Tab tab) {
   1176         for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
   1177             mSelectedListeners.get(i).onTabReselected(tab);
   1178         }
   1179     }
   1180 
   1181     private int calculateScrollXForTab(int position, float positionOffset) {
   1182         if (mMode == MODE_SCROLLABLE) {
   1183             final View selectedChild = mTabStrip.getChildAt(position);
   1184             final View nextChild = position + 1 < mTabStrip.getChildCount()
   1185                     ? mTabStrip.getChildAt(position + 1)
   1186                     : null;
   1187             final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
   1188             final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
   1189 
   1190             // base scroll amount: places center of tab in center of parent
   1191             int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (getWidth() / 2);
   1192             // offset amount: fraction of the distance between centers of tabs
   1193             int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset);
   1194 
   1195             return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
   1196                     ? scrollBase + scrollOffset
   1197                     : scrollBase - scrollOffset;
   1198         }
   1199         return 0;
   1200     }
   1201 
   1202     private void applyModeAndGravity() {
   1203         int paddingStart = 0;
   1204         if (mMode == MODE_SCROLLABLE) {
   1205             // If we're scrollable, or fixed at start, inset using padding
   1206             paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
   1207         }
   1208         ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
   1209 
   1210         switch (mMode) {
   1211             case MODE_FIXED:
   1212                 mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
   1213                 break;
   1214             case MODE_SCROLLABLE:
   1215                 mTabStrip.setGravity(GravityCompat.START);
   1216                 break;
   1217         }
   1218 
   1219         updateTabViews(true);
   1220     }
   1221 
   1222     void updateTabViews(final boolean requestLayout) {
   1223         for (int i = 0; i < mTabStrip.getChildCount(); i++) {
   1224             View child = mTabStrip.getChildAt(i);
   1225             child.setMinimumWidth(getTabMinWidth());
   1226             updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
   1227             if (requestLayout) {
   1228                 child.requestLayout();
   1229             }
   1230         }
   1231     }
   1232 
   1233     /**
   1234      * A tab in this layout. Instances can be created via {@link #newTab()}.
   1235      */
   1236     public static final class Tab {
   1237 
   1238         /**
   1239          * An invalid position for a tab.
   1240          *
   1241          * @see #getPosition()
   1242          */
   1243         public static final int INVALID_POSITION = -1;
   1244 
   1245         private Object mTag;
   1246         private Drawable mIcon;
   1247         private CharSequence mText;
   1248         private CharSequence mContentDesc;
   1249         private int mPosition = INVALID_POSITION;
   1250         private View mCustomView;
   1251 
   1252         TabLayout mParent;
   1253         TabView mView;
   1254 
   1255         Tab() {
   1256             // Private constructor
   1257         }
   1258 
   1259         /**
   1260          * @return This Tab's tag object.
   1261          */
   1262         @Nullable
   1263         public Object getTag() {
   1264             return mTag;
   1265         }
   1266 
   1267         /**
   1268          * Give this Tab an arbitrary object to hold for later use.
   1269          *
   1270          * @param tag Object to store
   1271          * @return The current instance for call chaining
   1272          */
   1273         @NonNull
   1274         public Tab setTag(@Nullable Object tag) {
   1275             mTag = tag;
   1276             return this;
   1277         }
   1278 
   1279 
   1280         /**
   1281          * Returns the custom view used for this tab.
   1282          *
   1283          * @see #setCustomView(View)
   1284          * @see #setCustomView(int)
   1285          */
   1286         @Nullable
   1287         public View getCustomView() {
   1288             return mCustomView;
   1289         }
   1290 
   1291         /**
   1292          * Set a custom view to be used for this tab.
   1293          * <p>
   1294          * If the provided view contains a {@link TextView} with an ID of
   1295          * {@link android.R.id#text1} then that will be updated with the value given
   1296          * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
   1297          * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
   1298          * the value given to {@link #setIcon(Drawable)}.
   1299          * </p>
   1300          *
   1301          * @param view Custom view to be used as a tab.
   1302          * @return The current instance for call chaining
   1303          */
   1304         @NonNull
   1305         public Tab setCustomView(@Nullable View view) {
   1306             mCustomView = view;
   1307             updateView();
   1308             return this;
   1309         }
   1310 
   1311         /**
   1312          * Set a custom view to be used for this tab.
   1313          * <p>
   1314          * If the inflated layout contains a {@link TextView} with an ID of
   1315          * {@link android.R.id#text1} then that will be updated with the value given
   1316          * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
   1317          * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
   1318          * the value given to {@link #setIcon(Drawable)}.
   1319          * </p>
   1320          *
   1321          * @param resId A layout resource to inflate and use as a custom tab view
   1322          * @return The current instance for call chaining
   1323          */
   1324         @NonNull
   1325         public Tab setCustomView(@LayoutRes int resId) {
   1326             final LayoutInflater inflater = LayoutInflater.from(mView.getContext());
   1327             return setCustomView(inflater.inflate(resId, mView, false));
   1328         }
   1329 
   1330         /**
   1331          * Return the icon associated with this tab.
   1332          *
   1333          * @return The tab's icon
   1334          */
   1335         @Nullable
   1336         public Drawable getIcon() {
   1337             return mIcon;
   1338         }
   1339 
   1340         /**
   1341          * Return the current position of this tab in the action bar.
   1342          *
   1343          * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
   1344          * the action bar.
   1345          */
   1346         public int getPosition() {
   1347             return mPosition;
   1348         }
   1349 
   1350         void setPosition(int position) {
   1351             mPosition = position;
   1352         }
   1353 
   1354         /**
   1355          * Return the text of this tab.
   1356          *
   1357          * @return The tab's text
   1358          */
   1359         @Nullable
   1360         public CharSequence getText() {
   1361             return mText;
   1362         }
   1363 
   1364         /**
   1365          * Set the icon displayed on this tab.
   1366          *
   1367          * @param icon The drawable to use as an icon
   1368          * @return The current instance for call chaining
   1369          */
   1370         @NonNull
   1371         public Tab setIcon(@Nullable Drawable icon) {
   1372             mIcon = icon;
   1373             updateView();
   1374             return this;
   1375         }
   1376 
   1377         /**
   1378          * Set the icon displayed on this tab.
   1379          *
   1380          * @param resId A resource ID referring to the icon that should be displayed
   1381          * @return The current instance for call chaining
   1382          */
   1383         @NonNull
   1384         public Tab setIcon(@DrawableRes int resId) {
   1385             if (mParent == null) {
   1386                 throw new IllegalArgumentException("Tab not attached to a TabLayout");
   1387             }
   1388             return setIcon(AppCompatResources.getDrawable(mParent.getContext(), resId));
   1389         }
   1390 
   1391         /**
   1392          * Set the text displayed on this tab. Text may be truncated if there is not room to display
   1393          * the entire string.
   1394          *
   1395          * @param text The text to display
   1396          * @return The current instance for call chaining
   1397          */
   1398         @NonNull
   1399         public Tab setText(@Nullable CharSequence text) {
   1400             mText = text;
   1401             updateView();
   1402             return this;
   1403         }
   1404 
   1405         /**
   1406          * Set the text displayed on this tab. Text may be truncated if there is not room to display
   1407          * the entire string.
   1408          *
   1409          * @param resId A resource ID referring to the text that should be displayed
   1410          * @return The current instance for call chaining
   1411          */
   1412         @NonNull
   1413         public Tab setText(@StringRes int resId) {
   1414             if (mParent == null) {
   1415                 throw new IllegalArgumentException("Tab not attached to a TabLayout");
   1416             }
   1417             return setText(mParent.getResources().getText(resId));
   1418         }
   1419 
   1420         /**
   1421          * Select this tab. Only valid if the tab has been added to the action bar.
   1422          */
   1423         public void select() {
   1424             if (mParent == null) {
   1425                 throw new IllegalArgumentException("Tab not attached to a TabLayout");
   1426             }
   1427             mParent.selectTab(this);
   1428         }
   1429 
   1430         /**
   1431          * Returns true if this tab is currently selected.
   1432          */
   1433         public boolean isSelected() {
   1434             if (mParent == null) {
   1435                 throw new IllegalArgumentException("Tab not attached to a TabLayout");
   1436             }
   1437             return mParent.getSelectedTabPosition() == mPosition;
   1438         }
   1439 
   1440         /**
   1441          * Set a description of this tab's content for use in accessibility support. If no content
   1442          * description is provided the title will be used.
   1443          *
   1444          * @param resId A resource ID referring to the description text
   1445          * @return The current instance for call chaining
   1446          * @see #setContentDescription(CharSequence)
   1447          * @see #getContentDescription()
   1448          */
   1449         @NonNull
   1450         public Tab setContentDescription(@StringRes int resId) {
   1451             if (mParent == null) {
   1452                 throw new IllegalArgumentException("Tab not attached to a TabLayout");
   1453             }
   1454             return setContentDescription(mParent.getResources().getText(resId));
   1455         }
   1456 
   1457         /**
   1458          * Set a description of this tab's content for use in accessibility support. If no content
   1459          * description is provided the title will be used.
   1460          *
   1461          * @param contentDesc Description of this tab's content
   1462          * @return The current instance for call chaining
   1463          * @see #setContentDescription(int)
   1464          * @see #getContentDescription()
   1465          */
   1466         @NonNull
   1467         public Tab setContentDescription(@Nullable CharSequence contentDesc) {
   1468             mContentDesc = contentDesc;
   1469             updateView();
   1470             return this;
   1471         }
   1472 
   1473         /**
   1474          * Gets a brief description of this tab's content for use in accessibility support.
   1475          *
   1476          * @return Description of this tab's content
   1477          * @see #setContentDescription(CharSequence)
   1478          * @see #setContentDescription(int)
   1479          */
   1480         @Nullable
   1481         public CharSequence getContentDescription() {
   1482             return mContentDesc;
   1483         }
   1484 
   1485         void updateView() {
   1486             if (mView != null) {
   1487                 mView.update();
   1488             }
   1489         }
   1490 
   1491         void reset() {
   1492             mParent = null;
   1493             mView = null;
   1494             mTag = null;
   1495             mIcon = null;
   1496             mText = null;
   1497             mContentDesc = null;
   1498             mPosition = INVALID_POSITION;
   1499             mCustomView = null;
   1500         }
   1501     }
   1502 
   1503     class TabView extends LinearLayout {
   1504         private Tab mTab;
   1505         private TextView mTextView;
   1506         private ImageView mIconView;
   1507 
   1508         private View mCustomView;
   1509         private TextView mCustomTextView;
   1510         private ImageView mCustomIconView;
   1511 
   1512         private int mDefaultMaxLines = 2;
   1513 
   1514         public TabView(Context context) {
   1515             super(context);
   1516             if (mTabBackgroundResId != 0) {
   1517                 ViewCompat.setBackground(
   1518                         this, AppCompatResources.getDrawable(context, mTabBackgroundResId));
   1519             }
   1520             ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
   1521                     mTabPaddingEnd, mTabPaddingBottom);
   1522             setGravity(Gravity.CENTER);
   1523             setOrientation(VERTICAL);
   1524             setClickable(true);
   1525             ViewCompat.setPointerIcon(this,
   1526                     PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND));
   1527         }
   1528 
   1529         @Override
   1530         public boolean performClick() {
   1531             final boolean handled = super.performClick();
   1532 
   1533             if (mTab != null) {
   1534                 if (!handled) {
   1535                     playSoundEffect(SoundEffectConstants.CLICK);
   1536                 }
   1537                 mTab.select();
   1538                 return true;
   1539             } else {
   1540                 return handled;
   1541             }
   1542         }
   1543 
   1544         @Override
   1545         public void setSelected(final boolean selected) {
   1546             final boolean changed = isSelected() != selected;
   1547 
   1548             super.setSelected(selected);
   1549 
   1550             if (changed && selected && Build.VERSION.SDK_INT < 16) {
   1551                 // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event
   1552                 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   1553             }
   1554 
   1555             // Always dispatch this to the child views, regardless of whether the value has
   1556             // changed
   1557             if (mTextView != null) {
   1558                 mTextView.setSelected(selected);
   1559             }
   1560             if (mIconView != null) {
   1561                 mIconView.setSelected(selected);
   1562             }
   1563             if (mCustomView != null) {
   1564                 mCustomView.setSelected(selected);
   1565             }
   1566         }
   1567 
   1568         @Override
   1569         public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   1570             super.onInitializeAccessibilityEvent(event);
   1571             // This view masquerades as an action bar tab.
   1572             event.setClassName(ActionBar.Tab.class.getName());
   1573         }
   1574 
   1575         @Override
   1576         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   1577             super.onInitializeAccessibilityNodeInfo(info);
   1578             // This view masquerades as an action bar tab.
   1579             info.setClassName(ActionBar.Tab.class.getName());
   1580         }
   1581 
   1582         @Override
   1583         public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) {
   1584             final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec);
   1585             final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec);
   1586             final int maxWidth = getTabMaxWidth();
   1587 
   1588             final int widthMeasureSpec;
   1589             final int heightMeasureSpec = origHeightMeasureSpec;
   1590 
   1591             if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED
   1592                     || specWidthSize > maxWidth)) {
   1593                 // If we have a max width and a given spec which is either unspecified or
   1594                 // larger than the max width, update the width spec using the same mode
   1595                 widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST);
   1596             } else {
   1597                 // Else, use the original width spec
   1598                 widthMeasureSpec = origWidthMeasureSpec;
   1599             }
   1600 
   1601             // Now lets measure
   1602             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1603 
   1604             // We need to switch the text size based on whether the text is spanning 2 lines or not
   1605             if (mTextView != null) {
   1606                 final Resources res = getResources();
   1607                 float textSize = mTabTextSize;
   1608                 int maxLines = mDefaultMaxLines;
   1609 
   1610                 if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
   1611                     // If the icon view is being displayed, we limit the text to 1 line
   1612                     maxLines = 1;
   1613                 } else if (mTextView != null && mTextView.getLineCount() > 1) {
   1614                     // Otherwise when we have text which wraps we reduce the text size
   1615                     textSize = mTabTextMultiLineSize;
   1616                 }
   1617 
   1618                 final float curTextSize = mTextView.getTextSize();
   1619                 final int curLineCount = mTextView.getLineCount();
   1620                 final int curMaxLines = TextViewCompat.getMaxLines(mTextView);
   1621 
   1622                 if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) {
   1623                     // We've got a new text size and/or max lines...
   1624                     boolean updateTextView = true;
   1625 
   1626                     if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) {
   1627                         // If we're in fixed mode, going up in text size and currently have 1 line
   1628                         // then it's very easy to get into an infinite recursion.
   1629                         // To combat that we check to see if the change in text size
   1630                         // will cause a line count change. If so, abort the size change and stick
   1631                         // to the smaller size.
   1632                         final Layout layout = mTextView.getLayout();
   1633                         if (layout == null || approximateLineWidth(layout, 0, textSize)
   1634                                 > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
   1635                             updateTextView = false;
   1636                         }
   1637                     }
   1638 
   1639                     if (updateTextView) {
   1640                         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
   1641                         mTextView.setMaxLines(maxLines);
   1642                         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1643                     }
   1644                 }
   1645             }
   1646         }
   1647 
   1648         void setTab(@Nullable final Tab tab) {
   1649             if (tab != mTab) {
   1650                 mTab = tab;
   1651                 update();
   1652             }
   1653         }
   1654 
   1655         void reset() {
   1656             setTab(null);
   1657             setSelected(false);
   1658         }
   1659 
   1660         final void update() {
   1661             final Tab tab = mTab;
   1662             final View custom = tab != null ? tab.getCustomView() : null;
   1663             if (custom != null) {
   1664                 final ViewParent customParent = custom.getParent();
   1665                 if (customParent != this) {
   1666                     if (customParent != null) {
   1667                         ((ViewGroup) customParent).removeView(custom);
   1668                     }
   1669                     addView(custom);
   1670                 }
   1671                 mCustomView = custom;
   1672                 if (mTextView != null) {
   1673                     mTextView.setVisibility(GONE);
   1674                 }
   1675                 if (mIconView != null) {
   1676                     mIconView.setVisibility(GONE);
   1677                     mIconView.setImageDrawable(null);
   1678                 }
   1679 
   1680                 mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
   1681                 if (mCustomTextView != null) {
   1682                     mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView);
   1683                 }
   1684                 mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
   1685             } else {
   1686                 // We do not have a custom view. Remove one if it already exists
   1687                 if (mCustomView != null) {
   1688                     removeView(mCustomView);
   1689                     mCustomView = null;
   1690                 }
   1691                 mCustomTextView = null;
   1692                 mCustomIconView = null;
   1693             }
   1694 
   1695             if (mCustomView == null) {
   1696                 // If there isn't a custom view, we'll us our own in-built layouts
   1697                 if (mIconView == null) {
   1698                     ImageView iconView = (ImageView) LayoutInflater.from(getContext())
   1699                             .inflate(R.layout.design_layout_tab_icon, this, false);
   1700                     addView(iconView, 0);
   1701                     mIconView = iconView;
   1702                 }
   1703                 if (mTextView == null) {
   1704                     TextView textView = (TextView) LayoutInflater.from(getContext())
   1705                             .inflate(R.layout.design_layout_tab_text, this, false);
   1706                     addView(textView);
   1707                     mTextView = textView;
   1708                     mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView);
   1709                 }
   1710                 TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance);
   1711                 if (mTabTextColors != null) {
   1712                     mTextView.setTextColor(mTabTextColors);
   1713                 }
   1714                 updateTextAndIcon(mTextView, mIconView);
   1715             } else {
   1716                 // Else, we'll see if there is a TextView or ImageView present and update them
   1717                 if (mCustomTextView != null || mCustomIconView != null) {
   1718                     updateTextAndIcon(mCustomTextView, mCustomIconView);
   1719                 }
   1720             }
   1721 
   1722             // Finally update our selected state
   1723             setSelected(tab != null && tab.isSelected());
   1724         }
   1725 
   1726         private void updateTextAndIcon(@Nullable final TextView textView,
   1727                 @Nullable final ImageView iconView) {
   1728             final Drawable icon = mTab != null ? mTab.getIcon() : null;
   1729             final CharSequence text = mTab != null ? mTab.getText() : null;
   1730             final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null;
   1731 
   1732             if (iconView != null) {
   1733                 if (icon != null) {
   1734                     iconView.setImageDrawable(icon);
   1735                     iconView.setVisibility(VISIBLE);
   1736                     setVisibility(VISIBLE);
   1737                 } else {
   1738                     iconView.setVisibility(GONE);
   1739                     iconView.setImageDrawable(null);
   1740                 }
   1741                 iconView.setContentDescription(contentDesc);
   1742             }
   1743 
   1744             final boolean hasText = !TextUtils.isEmpty(text);
   1745             if (textView != null) {
   1746                 if (hasText) {
   1747                     textView.setText(text);
   1748                     textView.setVisibility(VISIBLE);
   1749                     setVisibility(VISIBLE);
   1750                 } else {
   1751                     textView.setVisibility(GONE);
   1752                     textView.setText(null);
   1753                 }
   1754                 textView.setContentDescription(contentDesc);
   1755             }
   1756 
   1757             if (iconView != null) {
   1758                 MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams());
   1759                 int bottomMargin = 0;
   1760                 if (hasText && iconView.getVisibility() == VISIBLE) {
   1761                     // If we're showing both text and icon, add some margin bottom to the icon
   1762                     bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON);
   1763                 }
   1764                 if (bottomMargin != lp.bottomMargin) {
   1765                     lp.bottomMargin = bottomMargin;
   1766                     iconView.requestLayout();
   1767                 }
   1768             }
   1769             TooltipCompat.setTooltipText(this, hasText ? null : contentDesc);
   1770         }
   1771 
   1772         public Tab getTab() {
   1773             return mTab;
   1774         }
   1775 
   1776         /**
   1777          * Approximates a given lines width with the new provided text size.
   1778          */
   1779         private float approximateLineWidth(Layout layout, int line, float textSize) {
   1780             return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize());
   1781         }
   1782     }
   1783 
   1784     private class SlidingTabStrip extends LinearLayout {
   1785         private int mSelectedIndicatorHeight;
   1786         private final Paint mSelectedIndicatorPaint;
   1787 
   1788         int mSelectedPosition = -1;
   1789         float mSelectionOffset;
   1790 
   1791         private int mLayoutDirection = -1;
   1792 
   1793         private int mIndicatorLeft = -1;
   1794         private int mIndicatorRight = -1;
   1795 
   1796         private ValueAnimator mIndicatorAnimator;
   1797 
   1798         SlidingTabStrip(Context context) {
   1799             super(context);
   1800             setWillNotDraw(false);
   1801             mSelectedIndicatorPaint = new Paint();
   1802         }
   1803 
   1804         void setSelectedIndicatorColor(int color) {
   1805             if (mSelectedIndicatorPaint.getColor() != color) {
   1806                 mSelectedIndicatorPaint.setColor(color);
   1807                 ViewCompat.postInvalidateOnAnimation(this);
   1808             }
   1809         }
   1810 
   1811         void setSelectedIndicatorHeight(int height) {
   1812             if (mSelectedIndicatorHeight != height) {
   1813                 mSelectedIndicatorHeight = height;
   1814                 ViewCompat.postInvalidateOnAnimation(this);
   1815             }
   1816         }
   1817 
   1818         boolean childrenNeedLayout() {
   1819             for (int i = 0, z = getChildCount(); i < z; i++) {
   1820                 final View child = getChildAt(i);
   1821                 if (child.getWidth() <= 0) {
   1822                     return true;
   1823                 }
   1824             }
   1825             return false;
   1826         }
   1827 
   1828         void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
   1829             if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
   1830                 mIndicatorAnimator.cancel();
   1831             }
   1832 
   1833             mSelectedPosition = position;
   1834             mSelectionOffset = positionOffset;
   1835             updateIndicatorPosition();
   1836         }
   1837 
   1838         float getIndicatorPosition() {
   1839             return mSelectedPosition + mSelectionOffset;
   1840         }
   1841 
   1842         @Override
   1843         public void onRtlPropertiesChanged(int layoutDirection) {
   1844             super.onRtlPropertiesChanged(layoutDirection);
   1845 
   1846             // Workaround for a bug before Android M where LinearLayout did not relayout itself when
   1847             // layout direction changed.
   1848             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
   1849                 //noinspection WrongConstant
   1850                 if (mLayoutDirection != layoutDirection) {
   1851                     requestLayout();
   1852                     mLayoutDirection = layoutDirection;
   1853                 }
   1854             }
   1855         }
   1856 
   1857         @Override
   1858         protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
   1859             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1860 
   1861             if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
   1862                 // HorizontalScrollView will first measure use with UNSPECIFIED, and then with
   1863                 // EXACTLY. Ignore the first call since anything we do will be overwritten anyway
   1864                 return;
   1865             }
   1866 
   1867             if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
   1868                 final int count = getChildCount();
   1869 
   1870                 // First we'll find the widest tab
   1871                 int largestTabWidth = 0;
   1872                 for (int i = 0, z = count; i < z; i++) {
   1873                     View child = getChildAt(i);
   1874                     if (child.getVisibility() == VISIBLE) {
   1875                         largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
   1876                     }
   1877                 }
   1878 
   1879                 if (largestTabWidth <= 0) {
   1880                     // If we don't have a largest child yet, skip until the next measure pass
   1881                     return;
   1882                 }
   1883 
   1884                 final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
   1885                 boolean remeasure = false;
   1886 
   1887                 if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
   1888                     // If the tabs fit within our width minus gutters, we will set all tabs to have
   1889                     // the same width
   1890                     for (int i = 0; i < count; i++) {
   1891                         final LinearLayout.LayoutParams lp =
   1892                                 (LayoutParams) getChildAt(i).getLayoutParams();
   1893                         if (lp.width != largestTabWidth || lp.weight != 0) {
   1894                             lp.width = largestTabWidth;
   1895                             lp.weight = 0;
   1896                             remeasure = true;
   1897                         }
   1898                     }
   1899                 } else {
   1900                     // If the tabs will wrap to be larger than the width minus gutters, we need
   1901                     // to switch to GRAVITY_FILL
   1902                     mTabGravity = GRAVITY_FILL;
   1903                     updateTabViews(false);
   1904                     remeasure = true;
   1905                 }
   1906 
   1907                 if (remeasure) {
   1908                     // Now re-measure after our changes
   1909                     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   1910                 }
   1911             }
   1912         }
   1913 
   1914         @Override
   1915         protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1916             super.onLayout(changed, l, t, r, b);
   1917 
   1918             if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
   1919                 // If we're currently running an animation, lets cancel it and start a
   1920                 // new animation with the remaining duration
   1921                 mIndicatorAnimator.cancel();
   1922                 final long duration = mIndicatorAnimator.getDuration();
   1923                 animateIndicatorToPosition(mSelectedPosition,
   1924                         Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));
   1925             } else {
   1926                 // If we've been layed out, update the indicator position
   1927                 updateIndicatorPosition();
   1928             }
   1929         }
   1930 
   1931         private void updateIndicatorPosition() {
   1932             final View selectedTitle = getChildAt(mSelectedPosition);
   1933             int left, right;
   1934 
   1935             if (selectedTitle != null && selectedTitle.getWidth() > 0) {
   1936                 left = selectedTitle.getLeft();
   1937                 right = selectedTitle.getRight();
   1938 
   1939                 if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
   1940                     // Draw the selection partway between the tabs
   1941                     View nextTitle = getChildAt(mSelectedPosition + 1);
   1942                     left = (int) (mSelectionOffset * nextTitle.getLeft() +
   1943                             (1.0f - mSelectionOffset) * left);
   1944                     right = (int) (mSelectionOffset * nextTitle.getRight() +
   1945                             (1.0f - mSelectionOffset) * right);
   1946                 }
   1947             } else {
   1948                 left = right = -1;
   1949             }
   1950 
   1951             setIndicatorPosition(left, right);
   1952         }
   1953 
   1954         void setIndicatorPosition(int left, int right) {
   1955             if (left != mIndicatorLeft || right != mIndicatorRight) {
   1956                 // If the indicator's left/right has changed, invalidate
   1957                 mIndicatorLeft = left;
   1958                 mIndicatorRight = right;
   1959                 ViewCompat.postInvalidateOnAnimation(this);
   1960             }
   1961         }
   1962 
   1963         void animateIndicatorToPosition(final int position, int duration) {
   1964             if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
   1965                 mIndicatorAnimator.cancel();
   1966             }
   1967 
   1968             final boolean isRtl = ViewCompat.getLayoutDirection(this)
   1969                     == ViewCompat.LAYOUT_DIRECTION_RTL;
   1970 
   1971             final View targetView = getChildAt(position);
   1972             if (targetView == null) {
   1973                 // If we don't have a view, just update the position now and return
   1974                 updateIndicatorPosition();
   1975                 return;
   1976             }
   1977 
   1978             final int targetLeft = targetView.getLeft();
   1979             final int targetRight = targetView.getRight();
   1980             final int startLeft;
   1981             final int startRight;
   1982 
   1983             if (Math.abs(position - mSelectedPosition) <= 1) {
   1984                 // If the views are adjacent, we'll animate from edge-to-edge
   1985                 startLeft = mIndicatorLeft;
   1986                 startRight = mIndicatorRight;
   1987             } else {
   1988                 // Else, we'll just grow from the nearest edge
   1989                 final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
   1990                 if (position < mSelectedPosition) {
   1991                     // We're going end-to-start
   1992                     if (isRtl) {
   1993                         startLeft = startRight = targetLeft - offset;
   1994                     } else {
   1995                         startLeft = startRight = targetRight + offset;
   1996                     }
   1997                 } else {
   1998                     // We're going start-to-end
   1999                     if (isRtl) {
   2000                         startLeft = startRight = targetRight + offset;
   2001                     } else {
   2002                         startLeft = startRight = targetLeft - offset;
   2003                     }
   2004                 }
   2005             }
   2006 
   2007             if (startLeft != targetLeft || startRight != targetRight) {
   2008                 ValueAnimator animator = mIndicatorAnimator = new ValueAnimator();
   2009                 animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
   2010                 animator.setDuration(duration);
   2011                 animator.setFloatValues(0, 1);
   2012                 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   2013                     @Override
   2014                     public void onAnimationUpdate(ValueAnimator animator) {
   2015                         final float fraction = animator.getAnimatedFraction();
   2016                         setIndicatorPosition(
   2017                                 AnimationUtils.lerp(startLeft, targetLeft, fraction),
   2018                                 AnimationUtils.lerp(startRight, targetRight, fraction));
   2019                     }
   2020                 });
   2021                 animator.addListener(new AnimatorListenerAdapter() {
   2022                     @Override
   2023                     public void onAnimationEnd(Animator animator) {
   2024                         mSelectedPosition = position;
   2025                         mSelectionOffset = 0f;
   2026                     }
   2027                 });
   2028                 animator.start();
   2029             }
   2030         }
   2031 
   2032         @Override
   2033         public void draw(Canvas canvas) {
   2034             super.draw(canvas);
   2035 
   2036             // Thick colored underline below the current selection
   2037             if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
   2038                 canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
   2039                         mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
   2040             }
   2041         }
   2042     }
   2043 
   2044     private static ColorStateList createColorStateList(int defaultColor, int selectedColor) {
   2045         final int[][] states = new int[2][];
   2046         final int[] colors = new int[2];
   2047         int i = 0;
   2048 
   2049         states[i] = SELECTED_STATE_SET;
   2050         colors[i] = selectedColor;
   2051         i++;
   2052 
   2053         // Default enabled state
   2054         states[i] = EMPTY_STATE_SET;
   2055         colors[i] = defaultColor;
   2056         i++;
   2057 
   2058         return new ColorStateList(states, colors);
   2059     }
   2060 
   2061     private int getDefaultHeight() {
   2062         boolean hasIconAndText = false;
   2063         for (int i = 0, count = mTabs.size(); i < count; i++) {
   2064             Tab tab = mTabs.get(i);
   2065             if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) {
   2066                 hasIconAndText = true;
   2067                 break;
   2068             }
   2069         }
   2070         return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT;
   2071     }
   2072 
   2073     private int getTabMinWidth() {
   2074         if (mRequestedTabMinWidth != INVALID_WIDTH) {
   2075             // If we have been given a min width, use it
   2076             return mRequestedTabMinWidth;
   2077         }
   2078         // Else, we'll use the default value
   2079         return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;
   2080     }
   2081 
   2082     @Override
   2083     public LayoutParams generateLayoutParams(AttributeSet attrs) {
   2084         // We don't care about the layout params of any views added to us, since we don't actually
   2085         // add them. The only view we add is the SlidingTabStrip, which is done manually.
   2086         // We return the default layout params so that we don't blow up if we're given a TabItem
   2087         // without android:layout_* values.
   2088         return generateDefaultLayoutParams();
   2089     }
   2090 
   2091     int getTabMaxWidth() {
   2092         return mTabMaxWidth;
   2093     }
   2094 
   2095     /**
   2096      * A {@link ViewPager.OnPageChangeListener} class which contains the
   2097      * necessary calls back to the provided {@link TabLayout} so that the tab position is
   2098      * kept in sync.
   2099      *
   2100      * <p>This class stores the provided TabLayout weakly, meaning that you can use
   2101      * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
   2102      * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
   2103      * not cause a leak.
   2104      */
   2105     public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
   2106         private final WeakReference<TabLayout> mTabLayoutRef;
   2107         private int mPreviousScrollState;
   2108         private int mScrollState;
   2109 
   2110         public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
   2111             mTabLayoutRef = new WeakReference<>(tabLayout);
   2112         }
   2113 
   2114         @Override
   2115         public void onPageScrollStateChanged(final int state) {
   2116             mPreviousScrollState = mScrollState;
   2117             mScrollState = state;
   2118         }
   2119 
   2120         @Override
   2121         public void onPageScrolled(final int position, final float positionOffset,
   2122                 final int positionOffsetPixels) {
   2123             final TabLayout tabLayout = mTabLayoutRef.get();
   2124             if (tabLayout != null) {
   2125                 // Only update the text selection if we're not settling, or we are settling after
   2126                 // being dragged
   2127                 final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
   2128                         mPreviousScrollState == SCROLL_STATE_DRAGGING;
   2129                 // Update the indicator if we're not settling after being idle. This is caused
   2130                 // from a setCurrentItem() call and will be handled by an animation from
   2131                 // onPageSelected() instead.
   2132                 final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
   2133                         && mPreviousScrollState == SCROLL_STATE_IDLE);
   2134                 tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
   2135             }
   2136         }
   2137 
   2138         @Override
   2139         public void onPageSelected(final int position) {
   2140             final TabLayout tabLayout = mTabLayoutRef.get();
   2141             if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
   2142                     && position < tabLayout.getTabCount()) {
   2143                 // Select the tab, only updating the indicator if we're not being dragged/settled
   2144                 // (since onPageScrolled will handle that).
   2145                 final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
   2146                         || (mScrollState == SCROLL_STATE_SETTLING
   2147                         && mPreviousScrollState == SCROLL_STATE_IDLE);
   2148                 tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
   2149             }
   2150         }
   2151 
   2152         void reset() {
   2153             mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
   2154         }
   2155     }
   2156 
   2157     /**
   2158      * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back
   2159      * to the provided {@link ViewPager} so that the tab position is kept in sync.
   2160      */
   2161     public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
   2162         private final ViewPager mViewPager;
   2163 
   2164         public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
   2165             mViewPager = viewPager;
   2166         }
   2167 
   2168         @Override
   2169         public void onTabSelected(TabLayout.Tab tab) {
   2170             mViewPager.setCurrentItem(tab.getPosition());
   2171         }
   2172 
   2173         @Override
   2174         public void onTabUnselected(TabLayout.Tab tab) {
   2175             // No-op
   2176         }
   2177 
   2178         @Override
   2179         public void onTabReselected(TabLayout.Tab tab) {
   2180             // No-op
   2181         }
   2182     }
   2183 
   2184     private class PagerAdapterObserver extends DataSetObserver {
   2185         PagerAdapterObserver() {
   2186         }
   2187 
   2188         @Override
   2189         public void onChanged() {
   2190             populateFromPagerAdapter();
   2191         }
   2192 
   2193         @Override
   2194         public void onInvalidated() {
   2195             populateFromPagerAdapter();
   2196         }
   2197     }
   2198 
   2199     private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener {
   2200         private boolean mAutoRefresh;
   2201 
   2202         AdapterChangeListener() {
   2203         }
   2204 
   2205         @Override
   2206         public void onAdapterChanged(@NonNull ViewPager viewPager,
   2207                 @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) {
   2208             if (mViewPager == viewPager) {
   2209                 setPagerAdapter(newAdapter, mAutoRefresh);
   2210             }
   2211         }
   2212 
   2213         void setAutoRefresh(boolean autoRefresh) {
   2214             mAutoRefresh = autoRefresh;
   2215         }
   2216     }
   2217 }
   2218