1320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/*
2320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Copyright (C) 2015 The Android Open Source Project
3320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
4320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * you may not use this file except in compliance with the License.
6320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You may obtain a copy of the License at
7320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
8320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
10320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Unless required by applicable law or agreed to in writing, software
11320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * See the License for the specific language governing permissions and
14320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * limitations under the License.
15320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */
16320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
17320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespackage android.support.design.widget;
18320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
19320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.annotation.TargetApi;
20320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.Context;
21320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.ColorStateList;
22c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.content.res.Resources;
23320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.TypedArray;
2438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.database.DataSetObserver;
25320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Canvas;
26320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Paint;
273be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banesimport android.graphics.Rect;
28320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.drawable.Drawable;
29320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.os.Build;
3084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banesimport android.support.annotation.ColorInt;
3140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.DrawableRes;
32320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.annotation.IntDef;
3340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.LayoutRes;
3440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.NonNull;
3540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.Nullable;
3640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.StringRes;
37320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.design.R;
3838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.support.v4.util.Pools;
39320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.GravityCompat;
40320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.PagerAdapter;
41320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewCompat;
42320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewPager;
43092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banesimport android.support.v4.widget.TextViewCompat;
44320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v7.app.ActionBar;
4566698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport android.support.v7.widget.AppCompatDrawableManager;
46c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.text.Layout;
47320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.text.TextUtils;
48320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.util.AttributeSet;
49c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.util.TypedValue;
50320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.Gravity;
51320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.LayoutInflater;
52320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.View;
53320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewGroup;
54320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewParent;
55320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityEvent;
56320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityNodeInfo;
57320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.HorizontalScrollView;
58320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.ImageView;
59320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.LinearLayout;
60320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.TextView;
61320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.Toast;
62320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
63320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.Retention;
64320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.RetentionPolicy;
65745636602593f0b8ae508eb10266a3c7c675e746Chris Banesimport java.lang.ref.WeakReference;
66320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.ArrayList;
67320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.Iterator;
68320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
6938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
7038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
7138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
7238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
73320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/**
749fb154338a62edc2c57dc036895199d6f1769400Chris Banes * TabLayout provides a horizontal layout to display tabs.
759fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
769fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Population of the tabs to display is
77320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can
78320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)}
799fb154338a62edc2c57dc036895199d6f1769400Chris Banes * respectively. To display the tab, you need to add it to the layout via one of the
809fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #addTab(Tab)} methods. For example:
81320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <pre>
82320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * TabLayout tabLayout = ...;
83320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
84320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
85320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
86320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </pre>
87320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
88320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * notified when any tab's selection state has been changed.
89320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
904cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <p>You can also add items to TabLayout in your layout through the use of {@link TabItem}.
914cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * An example usage is like so:</p>
924cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
934cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <pre>
944cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * &lt;android.support.design.widget.TabLayout
954cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *         android:layout_height=&quot;wrap_content&quot;
964cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *         android:layout_width=&quot;match_parent&quot;&gt;
974cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
984cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *     &lt;android.support.design.widget.TabItem
994cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *             android:text=&quot;@string/tab_text&quot;/&gt;
1004cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
1014cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *     &lt;android.support.design.widget.TabItem
1024cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *             android:icon=&quot;@drawable/ic_android&quot;/&gt;
1034cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
1044cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * &lt;/android.support.design.widget.TabLayout&gt;
1054cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * </pre>
1064cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
1078f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <h3>ViewPager integration</h3>
1088f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <p>
1098f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * If you're using a {@link android.support.v4.view.ViewPager} together
1108f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together.
1118f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p>
1128f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *
1138f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <p>
1148f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * This view also supports being used as part of a ViewPager's decor, and can be added
1158f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * directly to the ViewPager in a layout resource file like so:</p>
1168f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *
1178f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <pre>
1188f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * &lt;android.support.v4.view.ViewPager
1198f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *     android:layout_width=&quot;match_parent&quot;
1208f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *     android:layout_height=&quot;match_parent&quot;&gt;
1218f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *
1228f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *     &lt;android.support.design.widget.TabLayout
1238f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *         android:layout_width=&quot;match_parent&quot;
1248f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *         android:layout_height=&quot;wrap_content&quot;
1258f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *         android:layout_gravity=&quot;top&quot; /&gt;
1268f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes *
1278f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * &lt;/android.support.v4.view.ViewPager&gt;
1288f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * </pre>
1294cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
130320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
1314cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes *
1324cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPadding
1334cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart
1344cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop
1354cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd
1364cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom
1374cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart
1384cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabBackground
1394cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth
1404cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth
1414cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance
142320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */
1438f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes@ViewPager.DecorView
144320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespublic class TabLayout extends HorizontalScrollView {
145320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
146c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps
147c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private static final int DEFAULT_GAP_TEXT_ICON = 8; // dps
148cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private static final int INVALID_WIDTH = -1;
149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int DEFAULT_HEIGHT = 48; // dps
150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
151320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
152320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int MOTION_NON_ADJACENT_OFFSET = 24;
153320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int ANIMATION_DURATION = 300;
155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
15638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16);
15738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
158320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab
160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * labels and a larger number of tabs. They are best used for browsing contexts in touch
161320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * interfaces when users don’t need to directly compare the tab labels.
162320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_SCROLLABLE = 0;
167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs display all tabs concurrently and are best used with content that benefits from
170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * quick pivots between tabs. The maximum number of tabs is limited by the view’s width.
171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs have equal width, based on the widest tab label.
172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_FIXED = 1;
177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED})
182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface Mode {}
184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect
187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * when used with {@link #MODE_FIXED}.
188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_FILL = 0;
193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
194320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
195320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to lay out the tabs in the center of the {@link TabLayout}.
196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
200320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_CENTER = 1;
201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER})
206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface TabGravity {}
208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Callback interface invoked when a tab's selection state changes.
211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public interface OnTabSelectedListener {
213320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab enters the selected state.
216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was selected
218320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
219320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabSelected(Tab tab);
220320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
222320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab exits the selected state.
223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was unselected
225320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabUnselected(Tab tab);
227320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
228320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
229320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab that is already selected is chosen again by the user. Some applications
230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * may use this action to return to the top level of a category.
231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was reselected.
233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabReselected(Tab tab);
235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final ArrayList<Tab> mTabs = new ArrayList<>();
238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private Tab mSelectedTab;
239320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final SlidingTabStrip mTabStrip;
241320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingStart;
243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingTop;
244320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingEnd;
245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingBottom;
246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
24745cbb1d10c865209f373a148556191700d8ea345Chris Banes    private int mTabTextAppearance;
24845cbb1d10c865209f373a148556191700d8ea345Chris Banes    private ColorStateList mTabTextColors;
249c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private float mTabTextSize;
250c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private float mTabTextMultiLineSize;
25145cbb1d10c865209f373a148556191700d8ea345Chris Banes
252320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mTabBackgroundResId;
253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
254d67a60744b4a713ca37e219e349b55743a39272aChris Banes    private int mTabMaxWidth = Integer.MAX_VALUE;
255cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private final int mRequestedTabMinWidth;
256320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mRequestedTabMaxWidth;
257cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private final int mScrollableTabMinWidth;
258320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
259320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mContentInsetStart;
260320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
261320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabGravity;
262320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mMode;
263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
2646f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private OnTabSelectedListener mSelectedListener;
2656f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
2666f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private OnTabSelectedListener mCurrentVpSelectedListener;
267320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
268bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes    private ValueAnimatorCompat mScrollAnimator;
269bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
27038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private ViewPager mViewPager;
27138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private PagerAdapter mPagerAdapter;
27238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private DataSetObserver mPagerAdapterObserver;
27338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private TabLayoutOnPageChangeListener mPageChangeListener;
2748f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    private AdapterChangeListener mAdapterChangeListener;
2758f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    private boolean mSetupViewPagerImplicitly;
27638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
27738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    // Pool we use as a simple RecyclerBin
27838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12);
27938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
280320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context) {
281320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        this(context, null);
282320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
283320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
284320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs) {
285b3ba94bf8b5cdae24e5a09a83813d72f2e2d8c1aChris Banes        this(context, attrs, 0);
286320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
287320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
288320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super(context, attrs, defStyleAttr);
290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
291809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes        ThemeUtils.checkAppCompatTheme(context);
292809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes
293320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Disable the Scroll Bar
294320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        setHorizontalScrollBarEnabled(false);
295320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
296320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Add the TabStrip
297320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip = new SlidingTabStrip(context);
2984cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
2994cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                defStyleAttr, R.style.Widget_Design_TabLayout);
303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
304320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorHeight(
305320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
306320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
307320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
308320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
309320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
311320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingStart);
312320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
313320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingTop);
314320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
315320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingEnd);
316320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
317320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingBottom);
318320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
319c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
320c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                R.style.TextAppearance_Design_Tab);
321c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
322c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        // Text colors/sizes come from the text appearance first
323c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
3246759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance);
325c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        try {
3266759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski            mTabTextSize = ta.getDimensionPixelSize(
3276759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
3286759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski            mTabTextColors = ta.getColorStateList(
3296759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
330c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        } finally {
331c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            ta.recycle();
332c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        }
33345cbb1d10c865209f373a148556191700d8ea345Chris Banes
33445cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
33545cbb1d10c865209f373a148556191700d8ea345Chris Banes            // If we have an explicit text color set, use it instead
33645cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor);
33745cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
33845cbb1d10c865209f373a148556191700d8ea345Chris Banes
339320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
34045cbb1d10c865209f373a148556191700d8ea345Chris Banes            // We have an explicit selected text color set, so we need to make merge it with the
34145cbb1d10c865209f373a148556191700d8ea345Chris Banes            // current colors. This is exposed so that developers can use theme attributes to set
34245cbb1d10c865209f373a148556191700d8ea345Chris Banes            // this (theme attrs in ColorStateLists are Lollipop+)
34345cbb1d10c865209f373a148556191700d8ea345Chris Banes            final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
34445cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
345320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
347cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,
348cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                INVALID_WIDTH);
349cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,
350cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                INVALID_WIDTH);
351320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
354320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
355320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        a.recycle();
356320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
357cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        // TODO add attr for these
358cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        final Resources res = getResources();
359cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
360cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
361c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
362320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now apply the tab mode and gravity
363320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        applyModeAndGravity();
364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
365320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
366320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
36784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's color for the currently selected tab.
36884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
36984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param color color to use for the indicator
3704cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
3714cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor
37284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
37384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorColor(@ColorInt int color) {
37484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorColor(color);
37584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
37684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
37784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
37884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's height for the currently selected tab.
37984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
38084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param height height to use for the indicator in pixels
3814cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
3824cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight
38384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
38484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorHeight(int height) {
38584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorHeight(height);
38684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
38784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
38884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
390745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <p>
392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Calling this method does not update the selected tab, it is only used for drawing purposes.
393ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     *
394ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param position current scroll position
395ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
396ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param updateSelectedText Whether to update the text's selected state.
397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
398ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes    public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
3993fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        setScrollPosition(position, positionOffset, updateSelectedText, true);
4003fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes    }
4013fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
4023fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes    private void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
4033fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            boolean updateIndicatorPosition) {
4043fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        final int roundedPosition = Math.round(position + positionOffset);
4053fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
406320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
4083fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
4093fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Set the indicator position, if enabled
4103fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (updateIndicatorPosition) {
4113fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
412320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
4143fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Now update the scroll position, canceling any running animation
4153fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
4163fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            mScrollAnimator.cancel();
4173fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        }
418320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        scrollTo(calculateScrollXForTab(position, positionOffset), 0);
419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
4203fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Update the 'selected state' view as we scroll, if enabled
421ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        if (updateSelectedText) {
4223fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            setSelectedTabView(roundedPosition);
423ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        }
424320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
425320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
42609db0f00a693704eb747027def4279362c51894eChris Banes    private float getScrollPosition() {
42709db0f00a693704eb747027def4279362c51894eChris Banes        return mTabStrip.getIndicatorPosition();
42809db0f00a693704eb747027def4279362c51894eChris Banes    }
42909db0f00a693704eb747027def4279362c51894eChris Banes
430320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
431320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
432320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
433320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
434320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
435320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
43640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab) {
437320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, mTabs.isEmpty());
438320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
439320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
443320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
445320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
44740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position) {
448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, position, mTabs.isEmpty());
449320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
450320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
451320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
452320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
45740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, boolean setSelected) {
458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, setSelected);
463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, mTabs.size());
464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
475320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
47640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
477320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
478320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
479320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
480320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
481320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, position, setSelected);
482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, position);
483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
484320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
485320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
486320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
487320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
4884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    private void addTabFromItemView(@NonNull TabItem item) {
4894cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        final Tab tab = newTab();
4904cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        if (item.mText != null) {
4914cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes            tab.setText(item.mText);
4924cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        }
4934cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        if (item.mIcon != null) {
4944cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes            tab.setIcon(item.mIcon);
4954cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        }
4964cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        if (item.mCustomLayout != 0) {
4974cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes            tab.setCustomView(item.mCustomLayout);
4984cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        }
4993be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes        if (!TextUtils.isEmpty(item.getContentDescription())) {
5003be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            tab.setContentDescription(item.getContentDescription());
5013be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes        }
5024cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        addTab(tab);
5034cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
5044cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
505320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
5066f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and
5076f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
5086f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     */
5096f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    @Deprecated
5106f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) {
5116f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        // The logic in this method emulates what we had before support for multiple
5126f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        // registered listeners.
5136f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        if (mSelectedListener != null) {
5146f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            removeOnTabSelectedListener(mSelectedListener);
5156f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
5166f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        // Update the deprecated field so that we can remove the passed listener the next
5176f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        // time we're called
5186f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        mSelectedListener = listener;
5196f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        if (listener != null) {
5206f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            addOnTabSelectedListener(listener);
5216f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
5226f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    }
5236f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
5246f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    /**
5256f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection
5266f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * changes.
5276f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     *
5286f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * <p>Components that add a listener should take care to remove it when finished via
5296f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p>
530320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
5316f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * @param listener listener to add
532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
5336f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
5346f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        if (!mSelectedListeners.contains(listener)) {
5356f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mSelectedListeners.add(listener);
5366f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
5376f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    }
5386f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
5396f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    /**
5406f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via
5416f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * {@link #addOnTabSelectedListener(OnTabSelectedListener)}.
5426f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     *
5436f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     * @param listener listener to remove
5446f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes     */
5456f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
5466f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        mSelectedListeners.remove(listener);
547320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
548320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
549320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
550320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Create and return a new {@link Tab}. You need to manually add this using
551320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * {@link #addTab(Tab)} or a related method.
552320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
553320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return A new Tab
554320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #addTab(Tab)
555320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
55640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @NonNull
557320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab newTab() {
5583a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        Tab tab = sTabPool.acquire();
5593a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        if (tab == null) {
56050f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            tab = new Tab();
5613a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
56250f777a07774d0f24529e2888fcba95744cb0be1Chris Banes        tab.mParent = this;
5633a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        tab.mView = createTabView(tab);
5643a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        return tab;
565320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
566320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
567320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
568320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the number of tabs currently registered with the action bar.
569320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
570320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return Tab count
571320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
572320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabCount() {
573320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.size();
574320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
575320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
576320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
577320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the tab at the specified index.
578320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
57940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
580320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab getTabAt(int index) {
581320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.get(index);
582320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
583320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
584320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
5851a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * Returns the position of the current selected tab.
5861a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     *
5871a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * @return selected tab position, or {@code -1} if there isn't a selected tab.
5881a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     */
5891a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    public int getSelectedTabPosition() {
5901a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
5911a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    }
5921a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
5931a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    /**
594320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
595320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
596320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
597320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to remove
598320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
599320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTab(Tab tab) {
600320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
601320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab does not belong to this TabLayout.");
602320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
603320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
604320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabAt(tab.getPosition());
605320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
606320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
607320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
608320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
609320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
610320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
611320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position Position of the tab to remove
612320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
613320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTabAt(int position) {
614320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
615320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabViewAt(position);
616320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
61738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        final Tab removedTab = mTabs.remove(position);
618320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (removedTab != null) {
61938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            removedTab.reset();
62038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            sTabPool.release(removedTab);
621320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
622320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
623320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int newTabCount = mTabs.size();
624320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position; i < newTabCount; i++) {
625320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
626320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
627320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
628320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (selectedTabPosition == position) {
629320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
630320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
631320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
632320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
633320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
634320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove all tabs from the action bar and deselect the current tab.
635320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
636320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeAllTabs() {
637320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Remove all the views
63838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) {
63938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            removeTabViewAt(i);
64038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
64238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) {
64338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final Tab tab = i.next();
644320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            i.remove();
64538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            tab.reset();
64638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            sTabPool.release(tab);
647320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
648bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
649bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mSelectedTab = null;
650320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
651320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
652320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
653320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the behavior mode for the Tabs in this layout. The valid input options are:
654320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <ul>
655320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used
656320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * with content that benefits from quick pivots between tabs.</li>
657320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
658320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and can contain longer tab labels and a larger number of tabs. They are best used for
659320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * browsing contexts in touch interfaces when users don’t need to directly compare the tab
660745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li>
661320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * </ul>
662320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
663320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
6644cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
6654cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabMode
666320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
667320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabMode(@Mode int mode) {
668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mode != mMode) {
669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mMode = mode;
670320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
671320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
672320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
673320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
674320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the current mode used by this {@link TabLayout}.
676320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
677320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
678320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
679320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Mode
680320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabMode() {
681320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mMode;
682320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
683320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
684320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
685320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the gravity to use when laying out the tabs.
686320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
687320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
6884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
6894cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabGravity
690320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
691320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabGravity(@TabGravity int gravity) {
692320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mTabGravity != gravity) {
693320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabGravity = gravity;
694320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
695320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
696320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
697320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
698320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
699320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * The current gravity used for laying out tabs.
700320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
701320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
702320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
703320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @TabGravity
704320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabGravity() {
705320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabGravity;
706320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
707320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
708320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
70945cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
7104cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
7114cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @see #getTabTextColors()
712320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
71340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void setTabTextColors(@Nullable ColorStateList textColor) {
71445cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (mTabTextColors != textColor) {
71545cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = textColor;
71645cbb1d10c865209f373a148556191700d8ea345Chris Banes            updateAllTabs();
717320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
718320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
719320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
720320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
72145cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Gets the text colors for the different states (normal, selected) used for the tabs.
722320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
72340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
72445cbb1d10c865209f373a148556191700d8ea345Chris Banes    public ColorStateList getTabTextColors() {
72545cbb1d10c865209f373a148556191700d8ea345Chris Banes        return mTabTextColors;
72645cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
72745cbb1d10c865209f373a148556191700d8ea345Chris Banes
72845cbb1d10c865209f373a148556191700d8ea345Chris Banes    /**
72945cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
7304cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     *
7314cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor
7324cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes     * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor
73345cbb1d10c865209f373a148556191700d8ea345Chris Banes     */
73445cbb1d10c865209f373a148556191700d8ea345Chris Banes    public void setTabTextColors(int normalColor, int selectedColor) {
73545cbb1d10c865209f373a148556191700d8ea345Chris Banes        setTabTextColors(createColorStateList(normalColor, selectedColor));
73645cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
73745cbb1d10c865209f373a148556191700d8ea345Chris Banes
738745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
739745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
740745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
7413cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with
7423cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * auto-refresh enabled.</p>
7433cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     *
7443cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
7453cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     */
7463cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes    public void setupWithViewPager(@Nullable ViewPager viewPager) {
7473cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes        setupWithViewPager(viewPager, true);
7483cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes    }
7493cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes
7503cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes    /**
7513cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
7523cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     *
7533cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * <p>This method will link the given ViewPager and this TabLayout together so that
7543cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * changes in one are automatically reflected in the other. This includes scroll state changes
7553cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * and clicks. The tabs displayed in this layout will be populated
75638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * from the ViewPager adapter's page titles.</p>
75738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *
7583cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will
7593cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * trigger this layout to re-populate itself from the adapter's titles.</p>
760745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
76138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * <p>If the given ViewPager is non-null, it needs to already have a
76238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * {@link PagerAdapter} set.</p>
76338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *
7643cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * @param viewPager   the ViewPager to link to, or {@code null} to clear any previous link
7653cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's
7663cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes     *                    content changes
767745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
7683cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes    public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
7698f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        setupWithViewPager(viewPager, autoRefresh, false);
7708f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    }
7718f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
7728f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
7738f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            boolean implicitSetup) {
7748f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        if (mViewPager != null) {
77538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // If we've already been setup with a ViewPager, remove us from it
7768f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (mPageChangeListener != null) {
7778f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                mViewPager.removeOnPageChangeListener(mPageChangeListener);
7788f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
7798f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (mAdapterChangeListener != null) {
7808f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
7818f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
782745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
783745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
7846f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        if (mCurrentVpSelectedListener != null) {
7856f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            // If we already have a tab selected listener for the ViewPager, remove it
7866f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            removeOnTabSelectedListener(mCurrentVpSelectedListener);
7876f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mCurrentVpSelectedListener = null;
7886f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
7896f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
79038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (viewPager != null) {
79138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mViewPager = viewPager;
792112f648090b48c0d00a51983baf148dbbc800abbChris Banes
79338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Add our custom OnPageChangeListener to the ViewPager
79438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mPageChangeListener == null) {
79538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mPageChangeListener = new TabLayoutOnPageChangeListener(this);
79609db0f00a693704eb747027def4279362c51894eChris Banes            }
79738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPageChangeListener.reset();
79838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            viewPager.addOnPageChangeListener(mPageChangeListener);
79938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
80038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Now we'll add a tab selected listener to set ViewPager's current item
8016f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
8026f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            addOnTabSelectedListener(mCurrentVpSelectedListener);
80338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
8048f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            final PagerAdapter adapter = viewPager.getAdapter();
8058f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (adapter != null) {
8068f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                // Now we'll populate ourselves from the pager adapter, adding an observer if
8078f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                // autoRefresh is enabled
8088f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                setPagerAdapter(adapter, autoRefresh);
8098f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
8108f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
8118f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            // Add a listener so that we're notified of any adapter changes
8128f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (mAdapterChangeListener == null) {
8138f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                mAdapterChangeListener = new AdapterChangeListener();
8148f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
8158f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            mAdapterChangeListener.setAutoRefresh(autoRefresh);
8168f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            viewPager.addOnAdapterChangeListener(mAdapterChangeListener);
817617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes
818617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes            // Now update the scroll position to match the ViewPager's current item
819617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes            setScrollPosition(viewPager.getCurrentItem(), 0f, true);
82038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        } else {
82138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // We've been given a null ViewPager so we need to clear out the internal state,
82238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // listeners and observers
82338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mViewPager = null;
8243cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes            setPagerAdapter(null, false);
825112f648090b48c0d00a51983baf148dbbc800abbChris Banes        }
8268f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
8278f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        mSetupViewPagerImplicitly = implicitSetup;
828745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
829745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
830745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
83138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a TabLayout with a ViewPager
83238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *             together. When that method is used, the TabLayout will be automatically updated
83338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *             when the {@link PagerAdapter} is changed.
834745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
83538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    @Deprecated
83638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    public void setTabsFromPagerAdapter(@Nullable final PagerAdapter adapter) {
83738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        setPagerAdapter(adapter, false);
83838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
83938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
840090d6780d4a901b258f33bcaa53014ca06744857Chris Banes    @Override
841090d6780d4a901b258f33bcaa53014ca06744857Chris Banes    public boolean shouldDelayChildPressedState() {
842090d6780d4a901b258f33bcaa53014ca06744857Chris Banes        // Only delay the pressed state if the tabs can scroll
843090d6780d4a901b258f33bcaa53014ca06744857Chris Banes        return getTabScrollRange() > 0;
844090d6780d4a901b258f33bcaa53014ca06744857Chris Banes    }
845090d6780d4a901b258f33bcaa53014ca06744857Chris Banes
8468f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    @Override
8478f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    protected void onAttachedToWindow() {
8488f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        super.onAttachedToWindow();
8498f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
8508f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        if (mViewPager == null) {
8518f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            // If we don't have a ViewPager already, check if our parent is a ViewPager to
8528f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            // setup with it automatically
8538f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            final ViewParent vp = getParent();
8548f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (vp instanceof ViewPager) {
8558f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                // If we have a ViewPager parent and we've been added as part of its decor, let's
8568f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                // assume that we should automatically setup to display any titles
8578f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                setupWithViewPager((ViewPager) vp, true, true);
8588f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
8598f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        }
8608f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    }
8618f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
8628f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    @Override
8638f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    protected void onDetachedFromWindow() {
8648f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        super.onDetachedFromWindow();
8658f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
8668f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        if (mSetupViewPagerImplicitly) {
8678f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc
8688f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            setupWithViewPager(null);
8698f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            mSetupViewPagerImplicitly = false;
8708f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        }
8718f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    }
8728f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
873090d6780d4a901b258f33bcaa53014ca06744857Chris Banes    private int getTabScrollRange() {
874090d6780d4a901b258f33bcaa53014ca06744857Chris Banes        return Math.max(0, mTabStrip.getWidth() - getWidth() - getPaddingLeft()
875090d6780d4a901b258f33bcaa53014ca06744857Chris Banes                - getPaddingRight());
876090d6780d4a901b258f33bcaa53014ca06744857Chris Banes    }
877090d6780d4a901b258f33bcaa53014ca06744857Chris Banes
87838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
87938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (mPagerAdapter != null && mPagerAdapterObserver != null) {
88038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // If we already have a PagerAdapter, unregister our observer
88138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
88238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
88338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
88438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        mPagerAdapter = adapter;
88538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
88638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (addObserver && adapter != null) {
88738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Register our observer on the new adapter
88838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mPagerAdapterObserver == null) {
88938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mPagerAdapterObserver = new PagerAdapterObserver();
89038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
89138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            adapter.registerDataSetObserver(mPagerAdapterObserver);
89238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
89338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
89438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        // Finally make sure we reflect the new adapter
89538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        populateFromPagerAdapter();
89638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
89738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
89838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private void populateFromPagerAdapter() {
899745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        removeAllTabs();
90038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
90138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (mPagerAdapter != null) {
90238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final int adapterCount = mPagerAdapter.getCount();
90338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            for (int i = 0; i < adapterCount; i++) {
90438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
90538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
90638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
90738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Make sure we reflect the currently set ViewPager item
90838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mViewPager != null && adapterCount > 0) {
90938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final int curItem = mViewPager.getCurrentItem();
91038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
91138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                    selectTab(getTabAt(curItem));
91238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                }
91338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
914745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
915745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
916745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
91745cbb1d10c865209f373a148556191700d8ea345Chris Banes    private void updateAllTabs() {
9183a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        for (int i = 0, z = mTabs.size(); i < z; i++) {
9193a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            mTabs.get(i).updateView();
92045cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
921320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
922320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
92338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private TabView createTabView(@NonNull final Tab tab) {
92438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
92538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (tabView == null) {
92638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            tabView = new TabView(getContext());
92738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
92838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        tabView.setTab(tab);
929320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tabView.setFocusable(true);
930cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        tabView.setMinimumWidth(getTabMinWidth());
931320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return tabView;
932320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
933320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
934320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void configureTab(Tab tab, int position) {
935320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tab.setPosition(position);
936320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabs.add(position, tab);
937320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
938320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int count = mTabs.size();
939320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position + 1; i < count; i++) {
940320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
942320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
943320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
944320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, boolean setSelected) {
9453a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        final TabView tabView = tab.mView;
946320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, createLayoutParamsForTabs());
947320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
948320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
949320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
950320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
951320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
952320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, int position, boolean setSelected) {
9533a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        final TabView tabView = tab.mView;
954320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
955320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
956320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
957320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
958320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
959320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
9604cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    @Override
9614cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    public void addView(View child) {
9624cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        addViewInternal(child);
9634cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
9644cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
9654cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    @Override
9664cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    public void addView(View child, int index) {
9674cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        addViewInternal(child);
9684cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
9694cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
9704cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    @Override
9714cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    public void addView(View child, ViewGroup.LayoutParams params) {
9724cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        addViewInternal(child);
9734cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
9744cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
9754cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    @Override
9764cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    public void addView(View child, int index, ViewGroup.LayoutParams params) {
9774cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        addViewInternal(child);
9784cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
9794cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
9804cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    private void addViewInternal(final View child) {
9814cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        if (child instanceof TabItem) {
9824cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes            addTabFromItemView((TabItem) child);
9834cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        } else {
9844cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes            throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout");
9854cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        }
9864cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
9874cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
990320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        updateTabViewLayoutParams(lp);
992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return lp;
993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = 0;
998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 1;
999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
1000320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
1001320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 0;
1002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int dpToPx(int dps) {
1006320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return Math.round(getResources().getDisplayMetrics().density * dps);
1007320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1008320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1009320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Override
1010320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1011320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // If we have a MeasureSpec which allows us to decide our height, try and use the default
1012320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // height
1013c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
1014320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (MeasureSpec.getMode(heightMeasureSpec)) {
1015320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.AT_MOST:
1016320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
1017bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
1018320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        MeasureSpec.EXACTLY);
1019320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
1020320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.UNSPECIFIED:
1021bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
1022320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
1023320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1024320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1025cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
1026cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
1027cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // If we don't have an unspecified width spec, use the given size to calculate
1028cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // the max tab width
1029cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            mTabMaxWidth = mRequestedTabMaxWidth > 0
1030cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    ? mRequestedTabMaxWidth
1031cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
1032cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        }
1033cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1034320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now super measure itself using the (possibly) modified height spec
1035320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1036320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1037cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (getChildCount() == 1) {
1038320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're in fixed mode then we need to make the tab strip is the same width as us
1039320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // so we don't scroll
1040320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View child = getChildAt(0);
1041cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            boolean remeasure = false;
1042cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1043cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            switch (mMode) {
1044cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                case MODE_SCROLLABLE:
1045cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // We only need to resize the child if it's smaller than us. This is similar
1046cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // to fillViewport
1047cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = child.getMeasuredWidth() < getMeasuredWidth();
1048cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    break;
1049cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                case MODE_FIXED:
1050cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // Resize the child so that it doesn't scroll
1051cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = child.getMeasuredWidth() != getMeasuredWidth();
1052cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    break;
1053cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            }
1054320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1055cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            if (remeasure) {
1056cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                // Re-measure the child with a widthSpec set to be exactly our measure width
1057320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
1058320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        + getPaddingBottom(), child.getLayoutParams().height);
1059cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
1060cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        getMeasuredWidth(), MeasureSpec.EXACTLY);
1061320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1062320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1063320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1064320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1065320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1066320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void removeTabViewAt(int position) {
106738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        final TabView view = (TabView) mTabStrip.getChildAt(position);
1068320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.removeViewAt(position);
106938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (view != null) {
107038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            view.reset();
107138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mTabViewPool.release(view);
107238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
1073320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        requestLayout();
1074320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1075320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1076320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void animateToTab(int newPosition) {
1077320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (newPosition == Tab.INVALID_POSITION) {
1078320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
1079320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
10807c71281dc4879e7889c65283aabaeed68521fb14Alan Jeon
1081bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
1082bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                || mTabStrip.childrenNeedLayout()) {
1083320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we don't have a window token, or we haven't been laid out yet just draw the new
1084320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // position now
1085ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes            setScrollPosition(newPosition, 0f, true);
1086320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
1087320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1088320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1089320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int startScrollX = getScrollX();
1090320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int targetScrollX = calculateScrollXForTab(newPosition, 0);
1091320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1092320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (startScrollX != targetScrollX) {
1093bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            if (mScrollAnimator == null) {
1094bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator = ViewUtils.createAnimator();
1095bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
1096bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setDuration(ANIMATION_DURATION);
1097bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
1098bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    @Override
1099bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
1100bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        scrollTo(animator.getAnimatedIntValue(), 0);
1101bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    }
1102bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                });
1103bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
1104bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
1105bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.setIntValues(startScrollX, targetScrollX);
1106bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.start();
1107320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1108320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1109320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now animate the indicator
1110bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
1111320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1112320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1113320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void setSelectedTabView(int position) {
1114320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int tabCount = mTabStrip.getChildCount();
111509db0f00a693704eb747027def4279362c51894eChris Banes        if (position < tabCount && !mTabStrip.getChildAt(position).isSelected()) {
111609db0f00a693704eb747027def4279362c51894eChris Banes            for (int i = 0; i < tabCount; i++) {
111709db0f00a693704eb747027def4279362c51894eChris Banes                final View child = mTabStrip.getChildAt(i);
111809db0f00a693704eb747027def4279362c51894eChris Banes                child.setSelected(i == position);
111909db0f00a693704eb747027def4279362c51894eChris Banes            }
1120320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1121320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1122320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1123320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    void selectTab(Tab tab) {
112415ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes        selectTab(tab, true);
112515ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes    }
112615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes
11276f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    void selectTab(final Tab tab, boolean updateIndicator) {
11286f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        final Tab currentTab = mSelectedTab;
11296f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
11306f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        if (currentTab == tab) {
11316f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            if (currentTab != null) {
11326f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes                dispatchTabReselected(tab);
1133320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                animateToTab(tab.getPosition());
1134320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
113615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes            if (updateIndicator) {
1137854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
1138854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                if (newPosition != Tab.INVALID_POSITION) {
1139854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                    setSelectedTabView(newPosition);
1140854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                }
11416f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes                if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION)
114215ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                        && newPosition != Tab.INVALID_POSITION) {
114315ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    // If we don't currently have a tab, just draw the indicator
114415ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    setScrollPosition(newPosition, 0f, true);
114515ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                } else {
114615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    animateToTab(newPosition);
114715ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                }
1148320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
11496f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            dispatchTabUnselected(currentTab);
1150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedTab = tab;
11516f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            dispatchTabSelected(tab);
11526f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
11536f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    }
11546f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
11556f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private void dispatchTabSelected(@NonNull final Tab tab) {
11566f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
11576f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mSelectedListeners.get(i).onTabSelected(tab);
11586f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
11596f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    }
11606f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
11616f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private void dispatchTabUnselected(@NonNull final Tab tab) {
11626f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
11636f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mSelectedListeners.get(i).onTabUnselected(tab);
11646f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        }
11656f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    }
11666f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes
11676f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes    private void dispatchTabReselected(@NonNull final Tab tab) {
11686f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
11696f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes            mSelectedListeners.get(i).onTabReselected(tab);
1170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int calculateScrollXForTab(int position, float positionOffset) {
1174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
1175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedChild = mTabStrip.getChildAt(position);
1176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View nextChild = position + 1 < mTabStrip.getChildCount()
1177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    ? mTabStrip.getChildAt(position + 1)
1178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    : null;
1179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
1180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
1181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1182bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return selectedChild.getLeft()
1183bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f))
1184bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + (selectedChild.getWidth() / 2)
1185bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    - (getWidth() / 2);
1186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return 0;
1188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void applyModeAndGravity() {
1191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        int paddingStart = 0;
1192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
1193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're scrollable, or fixed at start, inset using padding
1194320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
1195320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
1197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (mMode) {
1199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_FIXED:
1200320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
1201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
1202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_SCROLLABLE:
1203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(GravityCompat.START);
1204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
1205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1207cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        updateTabViews(true);
1208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1210cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private void updateTabViews(final boolean requestLayout) {
1211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = 0; i < mTabStrip.getChildCount(); i++) {
1212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            View child = mTabStrip.getChildAt(i);
1213cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            child.setMinimumWidth(getTabMinWidth());
1214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
1215cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            if (requestLayout) {
1216cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                child.requestLayout();
1217cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            }
1218320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1219320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1220320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
1222320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * A tab in this layout. Instances can be created via {@link #newTab()}.
1223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
1224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final class Tab {
1225320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1227320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * An invalid position for a tab.
1228320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1229320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getPosition()
1230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public static final int INVALID_POSITION = -1;
1232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Object mTag;
1234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Drawable mIcon;
1235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mText;
1236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mContentDesc;
1237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mPosition = INVALID_POSITION;
1238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
1239320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
124050f777a07774d0f24529e2888fcba95744cb0be1Chris Banes        private TabLayout mParent;
12413a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        private TabView mView;
1242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
124350f777a07774d0f24529e2888fcba95744cb0be1Chris Banes        private Tab() {
124450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            // Private constructor
1245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return This Tab's tag object.
1249320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
125040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1251320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Object getTag() {
1252320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTag;
1253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1254320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1255320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1256320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Give this Tab an arbitrary object to hold for later use.
1257320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1258320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tag Object to store
1259320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1260320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
126140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
126240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setTag(@Nullable Object tag) {
1263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTag = tag;
1264320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1265320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1266320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
126721f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes
126821f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        /**
126921f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * Returns the custom view used for this tab.
127021f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         *
127121f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(View)
127221f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(int)
127321f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         */
127421f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        @Nullable
127521f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        public View getCustomView() {
1276320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mCustomView;
1277320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1278320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1279320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
128040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
128140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
128240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the provided view contains a {@link TextView} with an ID of
128340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
128440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
128540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
128640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
128740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
1288320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param view Custom view to be used as a tab.
1290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1291320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
129240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
129340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setCustomView(@Nullable View view) {
1294320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mCustomView = view;
12953a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
129669992cb40e18789f4f406ffc94c4ebbd2bc274f7Kirill Grouchnikov
129769992cb40e18789f4f406ffc94c4ebbd2bc274f7Kirill Grouchnikov            final boolean isSelected = (mParent.getSelectedTabPosition() == getPosition());
129869992cb40e18789f4f406ffc94c4ebbd2bc274f7Kirill Grouchnikov            mCustomView.setSelected(isSelected);
129969992cb40e18789f4f406ffc94c4ebbd2bc274f7Kirill Grouchnikov
1300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
130440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
130540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
130640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the inflated layout contains a {@link TextView} with an ID of
130740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
130840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
130940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
131040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
131140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
1312320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1313b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes         * @param resId A layout resource to inflate and use as a custom tab view
1314320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1315320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
131640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
1317b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes        public Tab setCustomView(@LayoutRes int resId) {
13183a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            final LayoutInflater inflater = LayoutInflater.from(mView.getContext());
13193a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            return setCustomView(inflater.inflate(resId, mView, false));
1320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1321320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1322320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1323320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the icon associated with this tab.
1324320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1325320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's icon
1326320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
132740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1328320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Drawable getIcon() {
1329320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mIcon;
1330320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1331320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1332320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1333320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the current position of this tab in the action bar.
1334320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1335320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
1336320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the action bar.
1337320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1338320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public int getPosition() {
1339320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mPosition;
1340320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1341320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1342320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setPosition(int position) {
1343320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mPosition = position;
1344320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1345320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1347320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the text of this tab.
1348320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1349320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's text
1350320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
135140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getText() {
1353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mText;
1354320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1355320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1356320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1357320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1358320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1359320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param icon The drawable to use as an icon
1360320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1361320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
136240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
136340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@Nullable Drawable icon) {
1364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mIcon = icon;
13653a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1366320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1367320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1368320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1369320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1370320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1371320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the icon that should be displayed
1373320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1374320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
137540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
137640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@DrawableRes int resId) {
137750f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            if (mParent == null) {
137850f777a07774d0f24529e2888fcba95744cb0be1Chris Banes                throw new IllegalArgumentException("Tab not attached to a TabLayout");
137950f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            }
13807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            return setIcon(AppCompatDrawableManager.get().getDrawable(mParent.getContext(), resId));
1381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1382320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1383320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1384320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1385320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1386320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1387320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param text The text to display
1388320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
139040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
139140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@Nullable CharSequence text) {
1392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mText = text;
13933a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1399320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1400320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1401320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the text that should be displayed
1402320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1403320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
140440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
140540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@StringRes int resId) {
140650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            if (mParent == null) {
140750f777a07774d0f24529e2888fcba95744cb0be1Chris Banes                throw new IllegalArgumentException("Tab not attached to a TabLayout");
140850f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            }
1409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setText(mParent.getResources().getText(resId));
1410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1411320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1412320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Select this tab. Only valid if the tab has been added to the action bar.
1414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1415320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void select() {
141650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            if (mParent == null) {
141750f777a07774d0f24529e2888fcba95744cb0be1Chris Banes                throw new IllegalArgumentException("Tab not attached to a TabLayout");
141850f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            }
1419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mParent.selectTab(this);
1420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1421320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
14231a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         * Returns true if this tab is currently selected.
14241a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         */
14251a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        public boolean isSelected() {
142650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            if (mParent == null) {
142750f777a07774d0f24529e2888fcba95744cb0be1Chris Banes                throw new IllegalArgumentException("Tab not attached to a TabLayout");
142850f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            }
14291a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes            return mParent.getSelectedTabPosition() == mPosition;
14301a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        }
14311a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
14321a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        /**
1433320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1434320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1435320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1436320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the description text
1437320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1438320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1439320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
144140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
144240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@StringRes int resId) {
144350f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            if (mParent == null) {
144450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes                throw new IllegalArgumentException("Tab not attached to a TabLayout");
144550f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            }
1446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setContentDescription(mParent.getResources().getText(resId));
1447320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1449320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1450320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1451320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1452320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param contentDesc Description of this tab's content
1454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
145840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
145940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@Nullable CharSequence contentDesc) {
1460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mContentDesc = contentDesc;
14613a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Gets a brief description of this tab's content for use in accessibility support.
1467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Description of this tab's content
1469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
147240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getContentDescription() {
1474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mContentDesc;
1475320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
147638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
14773a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        private void updateView() {
14783a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            if (mView != null) {
14793a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                mView.update();
14803a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            }
14813a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
14823a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
148338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
148450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes            mParent = null;
14853a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            mView = null;
148638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mTag = null;
148738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mIcon = null;
148838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mText = null;
148938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mContentDesc = null;
149038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPosition = INVALID_POSITION;
149138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mCustomView = null;
149238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
1493320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1494320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1495320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    class TabView extends LinearLayout implements OnLongClickListener {
149638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private Tab mTab;
1497320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private TextView mTextView;
1498320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private ImageView mIconView;
149940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
1500320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
150140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private TextView mCustomTextView;
150240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private ImageView mCustomIconView;
1503320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1504c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        private int mDefaultMaxLines = 2;
1505c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
150638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public TabView(Context context) {
1507320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1508320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mTabBackgroundResId != 0) {
15097e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes                setBackgroundDrawable(
15107e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes                        AppCompatDrawableManager.get().getDrawable(context, mTabBackgroundResId));
1511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1512320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
1513320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabPaddingEnd, mTabPaddingBottom);
1514320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setGravity(Gravity.CENTER);
1515c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            setOrientation(VERTICAL);
15163a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            setClickable(true);
15173a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
15183a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
15193a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        @Override
15203a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        public boolean performClick() {
15213a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            final boolean value = super.performClick();
15223a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
15233a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            if (mTab != null) {
15243a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                mTab.select();
15253a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                return true;
15263a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            } else {
15273a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                return value;
15283a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            }
1529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1530320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1531320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void setSelected(boolean selected) {
1533320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean changed = (isSelected() != selected);
1534320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.setSelected(selected);
1535320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (changed && selected) {
1536320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1537320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1538320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1539320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setSelected(selected);
1540320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1541320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1542320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setSelected(selected);
1543320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1544320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1545320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1546320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1547320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1548320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1549320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1550320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityEvent(event);
1551320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1552320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            event.setClassName(ActionBar.Tab.class.getName());
1553320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1554320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1555320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1556320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1557320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1558320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityNodeInfo(info);
1559320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1560320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            info.setClassName(ActionBar.Tab.class.getName());
1561320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1562320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1563320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
15644172f25e47c484612b50143da44878003db238d1Chris Banes        public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) {
15654172f25e47c484612b50143da44878003db238d1Chris Banes            final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec);
15664172f25e47c484612b50143da44878003db238d1Chris Banes            final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec);
1567cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            final int maxWidth = getTabMaxWidth();
15684172f25e47c484612b50143da44878003db238d1Chris Banes
15694172f25e47c484612b50143da44878003db238d1Chris Banes            final int widthMeasureSpec;
15704172f25e47c484612b50143da44878003db238d1Chris Banes            final int heightMeasureSpec = origHeightMeasureSpec;
15714172f25e47c484612b50143da44878003db238d1Chris Banes
15724172f25e47c484612b50143da44878003db238d1Chris Banes            if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED
15734172f25e47c484612b50143da44878003db238d1Chris Banes                    || specWidthSize > maxWidth)) {
15744172f25e47c484612b50143da44878003db238d1Chris Banes                // If we have a max width and a given spec which is either unspecified or
15754172f25e47c484612b50143da44878003db238d1Chris Banes                // larger than the max width, update the width spec using the same mode
1576c7f2aad754af4373c32fcab613de0ba164ada2a2Kirill Grouchnikov                widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST);
1577cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            } else {
15784172f25e47c484612b50143da44878003db238d1Chris Banes                // Else, use the original width spec
15794172f25e47c484612b50143da44878003db238d1Chris Banes                widthMeasureSpec = origWidthMeasureSpec;
1580320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1581c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
15824172f25e47c484612b50143da44878003db238d1Chris Banes            // Now lets measure
15834172f25e47c484612b50143da44878003db238d1Chris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
15844172f25e47c484612b50143da44878003db238d1Chris Banes
1585c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            // We need to switch the text size based on whether the text is spanning 2 lines or not
1586c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (mTextView != null) {
1587c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final Resources res = getResources();
1588c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                float textSize = mTabTextSize;
1589c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                int maxLines = mDefaultMaxLines;
1590c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1591c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
1592c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // If the icon view is being displayed, we limit the text to 1 line
1593c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    maxLines = 1;
1594c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                } else if (mTextView != null && mTextView.getLineCount() > 1) {
1595c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // Otherwise when we have text which wraps we reduce the text size
1596c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    textSize = mTabTextMultiLineSize;
1597c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1598c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1599c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final float curTextSize = mTextView.getTextSize();
1600c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final int curLineCount = mTextView.getLineCount();
1601092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                final int curMaxLines = TextViewCompat.getMaxLines(mTextView);
1602c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1603092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) {
1604c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // We've got a new text size and/or max lines...
1605c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    boolean updateTextView = true;
1606c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1607c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) {
1608c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // If we're in fixed mode, going up in text size and currently have 1 line
1609c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // then it's very easy to get into an infinite recursion.
1610c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // To combat that we check to see if the change in text size
161127da60cbce61a72f4731cbe67071cbb87136d207Chris Banes                        // will cause a line count change. If so, abort the size change and stick
161227da60cbce61a72f4731cbe67071cbb87136d207Chris Banes                        // to the smaller size.
1613c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        final Layout layout = mTextView.getLayout();
161427da60cbce61a72f4731cbe67071cbb87136d207Chris Banes                        if (layout == null || approximateLineWidth(layout, 0, textSize)
161527da60cbce61a72f4731cbe67071cbb87136d207Chris Banes                                > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
1616c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                            updateTextView = false;
1617c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        }
1618c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    }
1619c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1620c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    if (updateTextView) {
1621c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
1622c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        mTextView.setMaxLines(maxLines);
1623c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1624c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    }
1625c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1626c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
1627320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1628320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
162938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void setTab(@Nullable final Tab tab) {
163038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (tab != mTab) {
163138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mTab = tab;
163238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                update();
163338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
163438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
163538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
163638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
163738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setTab(null);
163838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setSelected(false);
163938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
164038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
1641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final void update() {
1642320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Tab tab = mTab;
164338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final View custom = tab != null ? tab.getCustomView() : null;
1644320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (custom != null) {
1645320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final ViewParent customParent = custom.getParent();
1646320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (customParent != this) {
1647320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (customParent != null) {
1648320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        ((ViewGroup) customParent).removeView(custom);
1649320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1650320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    addView(custom);
1651320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1652320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mCustomView = custom;
1653320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1654320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setVisibility(GONE);
1655320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1656320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1657320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setVisibility(GONE);
1658320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setImageDrawable(null);
1659320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
166040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
166140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
1662c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (mCustomTextView != null) {
1663092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                    mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView);
1664c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
166540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
1666320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
166740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // We do not have a custom view. Remove one if it already exists
1668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mCustomView != null) {
1669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    removeView(mCustomView);
1670320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mCustomView = null;
1671320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
167240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = null;
167340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = null;
167440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
167640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (mCustomView == null) {
167740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // If there isn't a custom view, we'll us our own in-built layouts
167840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mIconView == null) {
167940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    ImageView iconView = (ImageView) LayoutInflater.from(getContext())
1680a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_icon, this, false);
168140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(iconView, 0);
168240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mIconView = iconView;
1683320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
168440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTextView == null) {
168540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    TextView textView = (TextView) LayoutInflater.from(getContext())
1686a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_text, this, false);
168740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(textView);
168840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView = textView;
1689092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                    mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView);
169040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
169140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mTextView.setTextAppearance(getContext(), mTabTextAppearance);
169240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTabTextColors != null) {
169340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView.setTextColor(mTabTextColors);
1694320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
169538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                updateTextAndIcon(mTextView, mIconView);
169640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
169740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // Else, we'll see if there is a TextView or ImageView present and update them
169840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mCustomTextView != null || mCustomIconView != null) {
169938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                    updateTextAndIcon(mCustomTextView, mCustomIconView);
170040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
170140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
170240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        }
1703320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
170438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void updateTextAndIcon(@Nullable final TextView textView,
170538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                @Nullable final ImageView iconView) {
170638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final Drawable icon = mTab != null ? mTab.getIcon() : null;
170738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final CharSequence text = mTab != null ? mTab.getText() : null;
170838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null;
170940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
171040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (iconView != null) {
171140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (icon != null) {
171240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(icon);
171340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(VISIBLE);
171440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
171540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                } else {
171640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(GONE);
171740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(null);
1718320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
171938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                iconView.setContentDescription(contentDesc);
172040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1721320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
172240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            final boolean hasText = !TextUtils.isEmpty(text);
172340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (textView != null) {
172440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (hasText) {
172540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(text);
172640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(VISIBLE);
172740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
1728320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
172940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(GONE);
173040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(null);
1731320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
173238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                textView.setContentDescription(contentDesc);
1733320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
173440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
1735c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (iconView != null) {
1736c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams());
1737c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                int bottomMargin = 0;
1738c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (hasText && iconView.getVisibility() == VISIBLE) {
1739c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // If we're showing both text and icon, add some margin bottom to the icon
1740c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON);
1741c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1742c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (bottomMargin != lp.bottomMargin) {
1743c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    lp.bottomMargin = bottomMargin;
1744c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    iconView.requestLayout();
1745c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1746c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
1747c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
174838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (!hasText && !TextUtils.isEmpty(contentDesc)) {
174940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(this);
175040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
175140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(null);
175240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setLongClickable(false);
175340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1754320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1755320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1756320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
17573be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes        public boolean onLongClick(final View v) {
1758320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int[] screenPos = new int[2];
17593be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            final Rect displayFrame = new Rect();
1760320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            getLocationOnScreen(screenPos);
17613be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            getWindowVisibleDisplayFrame(displayFrame);
1762320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1763320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Context context = getContext();
1764320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int width = getWidth();
1765320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int height = getHeight();
17663be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            final int midy = screenPos[1] + height / 2;
17673be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            int referenceX = screenPos[0] + width / 2;
17683be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
17693be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
17703be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                referenceX = screenWidth - referenceX; // mirror
17713be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            }
1772320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1773320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
1774320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    Toast.LENGTH_SHORT);
17753be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            if (midy < displayFrame.height()) {
17763be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                // Show below the tab view
17773be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
17783be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                        screenPos[1] + height - displayFrame.top);
17793be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            } else {
17803be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                // Show along the bottom center
17813be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes                cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
17823be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes            }
1783320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            cheatSheet.show();
1784320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return true;
1785320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1786320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1787320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Tab getTab() {
1788320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTab;
1789320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1790c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1791c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        /**
1792c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes         * Approximates a given lines width with the new provided text size.
1793c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes         */
1794c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        private float approximateLineWidth(Layout layout, int line, float textSize) {
1795c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize());
1796c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        }
1797320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1798320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1799320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private class SlidingTabStrip extends LinearLayout {
1800320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedIndicatorHeight;
1801320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final Paint mSelectedIndicatorPaint;
1802320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1803320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedPosition = -1;
1804320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private float mSelectionOffset;
1805320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1806320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorLeft = -1;
1807320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorRight = -1;
1808320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
18093fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        private ValueAnimatorCompat mIndicatorAnimator;
1810ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes
1811320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        SlidingTabStrip(Context context) {
1812320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1813320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setWillNotDraw(false);
1814320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedIndicatorPaint = new Paint();
1815320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1816320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1817320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorColor(int color) {
181809db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorPaint.getColor() != color) {
181909db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorPaint.setColor(color);
182009db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
182109db0f00a693704eb747027def4279362c51894eChris Banes            }
1822320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1823320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1824320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorHeight(int height) {
182509db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorHeight != height) {
182609db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorHeight = height;
182709db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
182809db0f00a693704eb747027def4279362c51894eChris Banes            }
1829320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1830320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1831bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        boolean childrenNeedLayout() {
1832bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            for (int i = 0, z = getChildCount(); i < z; i++) {
1833bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                final View child = getChildAt(i);
1834bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                if (child.getWidth() <= 0) {
1835bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    return true;
1836bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                }
1837bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
1838bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return false;
1839bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        }
1840bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
1841320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
18423fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
18433fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
18443fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            }
18453fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
1846320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedPosition = position;
1847320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectionOffset = positionOffset;
1848320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateIndicatorPosition();
1849320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1850320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
185109db0f00a693704eb747027def4279362c51894eChris Banes        float getIndicatorPosition() {
185209db0f00a693704eb747027def4279362c51894eChris Banes            return mSelectedPosition + mSelectionOffset;
185309db0f00a693704eb747027def4279362c51894eChris Banes        }
185409db0f00a693704eb747027def4279362c51894eChris Banes
1855320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1856320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1857320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1858320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1859320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
1860320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // HorizontalScrollView will first measure use with UNSPECIFIED, and then with
1861320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // EXACTLY. Ignore the first call since anything we do will be overwritten anyway
1862320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                return;
1863320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1864320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1865320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
1866320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int count = getChildCount();
1867320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1868cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                // First we'll find the widest tab
1869320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int largestTabWidth = 0;
1870320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                for (int i = 0, z = count; i < z; i++) {
1871cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    View child = getChildAt(i);
1872cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    if (child.getVisibility() == VISIBLE) {
1873cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
1874cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    }
1875320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1876320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1877320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth <= 0) {
1878320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If we don't have a largest child yet, skip until the next measure pass
1879320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    return;
1880320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1881320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1882320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
1883cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                boolean remeasure = false;
1884cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1885320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
1886320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs fit within our width minus gutters, we will set all tabs to have
1887320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // the same width
1888320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    for (int i = 0; i < count; i++) {
1889cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        final LinearLayout.LayoutParams lp =
1890cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                                (LayoutParams) getChildAt(i).getLayoutParams();
1891cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        if (lp.width != largestTabWidth || lp.weight != 0) {
1892cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            lp.width = largestTabWidth;
1893cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            lp.weight = 0;
1894cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            remeasure = true;
1895cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        }
1896320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1897320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1898320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs will wrap to be larger than the width minus gutters, we need
1899320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // to switch to GRAVITY_FILL
1900320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabGravity = GRAVITY_FILL;
1901cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    updateTabViews(false);
1902cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = true;
1903320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1904320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1905cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                if (remeasure) {
1906cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // Now re-measure after our changes
1907cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1908cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                }
1909320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1910320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1911320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1912320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1913320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1914320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onLayout(changed, l, t, r, b);
1915ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes
19163fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
1917ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // If we're currently running an animation, lets cancel it and start a
1918ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // new animation with the remaining duration
19193fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
19203fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                final long duration = mIndicatorAnimator.getDuration();
1921ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                animateIndicatorToPosition(mSelectedPosition,
19223fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                        Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));
1923ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes            } else {
1924ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // If we've been layed out, update the indicator position
1925ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                updateIndicatorPosition();
1926ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes            }
1927320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1928320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1929320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void updateIndicatorPosition() {
1930320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedTitle = getChildAt(mSelectedPosition);
1931320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            int left, right;
1932320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1933320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (selectedTitle != null && selectedTitle.getWidth() > 0) {
1934320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = selectedTitle.getLeft();
1935320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                right = selectedTitle.getRight();
1936320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1937320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
1938320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // Draw the selection partway between the tabs
1939320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    View nextTitle = getChildAt(mSelectedPosition + 1);
1940320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    left = (int) (mSelectionOffset * nextTitle.getLeft() +
1941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * left);
1942320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    right = (int) (mSelectionOffset * nextTitle.getRight() +
1943320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * right);
1944320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1945320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1946320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = right = -1;
1947320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1948320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1949320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setIndicatorPosition(left, right);
1950320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1951320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1952320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void setIndicatorPosition(int left, int right) {
1953320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (left != mIndicatorLeft || right != mIndicatorRight) {
1954320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the indicator's left/right has changed, invalidate
1955320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorLeft = left;
1956320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorRight = right;
1957320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                ViewCompat.postInvalidateOnAnimation(this);
1958320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1959320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1960320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1961320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void animateIndicatorToPosition(final int position, int duration) {
19623fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
19633fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
19643fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            }
19653fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
1966320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean isRtl = ViewCompat.getLayoutDirection(this)
1967320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    == ViewCompat.LAYOUT_DIRECTION_RTL;
1968320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1969320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View targetView = getChildAt(position);
19709c2232ee41f2db73648348e84cba637b892de617Chris Banes            if (targetView == null) {
19719c2232ee41f2db73648348e84cba637b892de617Chris Banes                // If we don't have a view, just update the position now and return
19729c2232ee41f2db73648348e84cba637b892de617Chris Banes                updateIndicatorPosition();
19739c2232ee41f2db73648348e84cba637b892de617Chris Banes                return;
19749c2232ee41f2db73648348e84cba637b892de617Chris Banes            }
19759c2232ee41f2db73648348e84cba637b892de617Chris Banes
1976320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetLeft = targetView.getLeft();
1977320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetRight = targetView.getRight();
1978320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startLeft;
1979320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startRight;
1980320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1981320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (Math.abs(position - mSelectedPosition) <= 1) {
1982320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the views are adjacent, we'll animate from edge-to-edge
1983320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startLeft = mIndicatorLeft;
1984320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startRight = mIndicatorRight;
1985320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1986320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // Else, we'll just grow from the nearest edge
1987320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
1988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (position < mSelectedPosition) {
1989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going end-to-start
1990320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
1992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
1993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going start-to-end
1997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
2000320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
2001320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
2002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
2003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
2004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
2005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (startLeft != targetLeft || startRight != targetRight) {
2006bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                ValueAnimatorCompat animator = mIndicatorAnimator = ViewUtils.createAnimator();
2007f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
2008f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setDuration(duration);
2009f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setFloatValues(0, 1);
2010f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
2011320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
2012f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
2013f44e88b81821e61a65886cc7027617ca8995070bChris Banes                        final float fraction = animator.getAnimatedFraction();
2014320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        setIndicatorPosition(
2015f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startLeft, targetLeft, fraction),
2016f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startRight, targetRight, fraction));
2017320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
2018f44e88b81821e61a65886cc7027617ca8995070bChris Banes                });
2019f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
2020320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
2021f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationEnd(ValueAnimatorCompat animator) {
2022320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectedPosition = position;
2023320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectionOffset = 0f;
2024320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
2025320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                });
2026f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.start();
2027320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
2028320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
2029320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
2030320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
2031442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes        public void draw(Canvas canvas) {
2032442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes            super.draw(canvas);
2033442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes
2034320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // Thick colored underline below the current selection
2035320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
2036320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
2037320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
2038320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
2039320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
2040320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
2041320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
204245cbb1d10c865209f373a148556191700d8ea345Chris Banes    private static ColorStateList createColorStateList(int defaultColor, int selectedColor) {
204345cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[][] states = new int[2][];
204445cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[] colors = new int[2];
204545cbb1d10c865209f373a148556191700d8ea345Chris Banes        int i = 0;
204645cbb1d10c865209f373a148556191700d8ea345Chris Banes
204745cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = SELECTED_STATE_SET;
204845cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = selectedColor;
204945cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
205045cbb1d10c865209f373a148556191700d8ea345Chris Banes
205145cbb1d10c865209f373a148556191700d8ea345Chris Banes        // Default enabled state
205245cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = EMPTY_STATE_SET;
205345cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = defaultColor;
205445cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
205545cbb1d10c865209f373a148556191700d8ea345Chris Banes
205645cbb1d10c865209f373a148556191700d8ea345Chris Banes        return new ColorStateList(states, colors);
205745cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
205845cbb1d10c865209f373a148556191700d8ea345Chris Banes
2059c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private int getDefaultHeight() {
2060c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        boolean hasIconAndText = false;
2061c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        for (int i = 0, count = mTabs.size(); i < count; i++) {
2062c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            Tab tab = mTabs.get(i);
2063c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) {
2064c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                hasIconAndText = true;
2065c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                break;
2066c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
206745cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
2068c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT;
206945cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
207045cbb1d10c865209f373a148556191700d8ea345Chris Banes
2071cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private int getTabMinWidth() {
2072cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (mRequestedTabMinWidth != INVALID_WIDTH) {
2073cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // If we have been given a min width, use it
2074cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            return mRequestedTabMinWidth;
2075cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        }
2076cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        // Else, we'll use the default value
2077cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;
2078cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    }
2079cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
20804cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    @Override
20814cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    public LayoutParams generateLayoutParams(AttributeSet attrs) {
20824cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        // We don't care about the layout params of any views added to us, since we don't actually
20834cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        // add them. The only view we add is the SlidingTabStrip, which is done manually.
20844cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        // We return the default layout params so that we don't blow up if we're given a TabItem
20854cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        // without android:layout_* values.
20864cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes        return generateDefaultLayoutParams();
20874cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes    }
20884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes
2089cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private int getTabMaxWidth() {
2090cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        return mTabMaxWidth;
2091cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    }
2092cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
2093745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
2094745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link ViewPager.OnPageChangeListener} class which contains the
2095745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * necessary calls back to the provided {@link TabLayout} so that the tab position is
2096745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * kept in sync.
2097745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
2098745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <p>This class stores the provided TabLayout weakly, meaning that you can use
2099745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
2100745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
2101745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * not cause a leak.
2102745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
2103745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
2104745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final WeakReference<TabLayout> mTabLayoutRef;
210514113e918762c4319dfde99e5b87873deeda9f03Chris Banes        private int mPreviousScrollState;
2106745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private int mScrollState;
2107745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2108745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
2109745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mTabLayoutRef = new WeakReference<>(tabLayout);
2110745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2111745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2112745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
21133cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes        public void onPageScrollStateChanged(final int state) {
211414113e918762c4319dfde99e5b87873deeda9f03Chris Banes            mPreviousScrollState = mScrollState;
2115745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mScrollState = state;
2116745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2117745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2118745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
21193cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes        public void onPageScrolled(final int position, final float positionOffset,
21203cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes                final int positionOffsetPixels) {
2121745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            final TabLayout tabLayout = mTabLayoutRef.get();
2122745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            if (tabLayout != null) {
212338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                // Only update the text selection if we're not settling, or we are settling after
212438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                // being dragged
212538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
212638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        mPreviousScrollState == SCROLL_STATE_DRAGGING;
21273fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // Update the indicator if we're not settling after being idle. This is caused
21283fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // from a setCurrentItem() call and will be handled by an animation from
21293fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // onPageSelected() instead.
213038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
213138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        && mPreviousScrollState == SCROLL_STATE_IDLE);
21323fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
2133745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            }
2134745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2135745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2136745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
21373cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes        public void onPageSelected(final int position) {
213814113e918762c4319dfde99e5b87873deeda9f03Chris Banes            final TabLayout tabLayout = mTabLayoutRef.get();
21393cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
21403cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes                    && position < tabLayout.getTabCount()) {
214114113e918762c4319dfde99e5b87873deeda9f03Chris Banes                // Select the tab, only updating the indicator if we're not being dragged/settled
214214113e918762c4319dfde99e5b87873deeda9f03Chris Banes                // (since onPageScrolled will handle that).
214338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
214438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        || (mScrollState == SCROLL_STATE_SETTLING
214538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        && mPreviousScrollState == SCROLL_STATE_IDLE);
21463fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
214714113e918762c4319dfde99e5b87873deeda9f03Chris Banes            }
2148745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
214938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
215038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
215138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
215238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
2153745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
2154745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2155745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
2156745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back
2157745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * to the provided {@link ViewPager} so that the tab position is kept in sync.
2158745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
2159745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
2160745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final ViewPager mViewPager;
2161745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2162745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
2163745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager = viewPager;
2164745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2165745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2166745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
2167745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabSelected(TabLayout.Tab tab) {
2168745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager.setCurrentItem(tab.getPosition());
2169745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2170745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2171745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
2172745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabUnselected(TabLayout.Tab tab) {
2173745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
2174745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2175745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
2176745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
2177745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabReselected(TabLayout.Tab tab) {
2178745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
2179745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
2180745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
2181745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
218238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private class PagerAdapterObserver extends DataSetObserver {
218338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        @Override
218438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public void onChanged() {
218538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            populateFromPagerAdapter();
218638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
218738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
218838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        @Override
218938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public void onInvalidated() {
219038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            populateFromPagerAdapter();
219138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
219238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
219338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
21948f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener {
21958f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        private boolean mAutoRefresh;
21968f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
21978f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        @Override
21988f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        public void onAdapterChanged(@NonNull ViewPager viewPager,
21998f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) {
22008f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            if (mViewPager == viewPager) {
22018f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes                setPagerAdapter(newAdapter, mAutoRefresh);
22028f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            }
22038f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        }
22048f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes
22058f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        void setAutoRefresh(boolean autoRefresh) {
22068f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes            mAutoRefresh = autoRefresh;
22078f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes        }
22088f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes    }
2209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes}
2210