TabLayout.java revision 09db0f00a693704eb747027def4279362c51894e
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;
22320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.TypedArray;
23320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Canvas;
24320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Paint;
25320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.drawable.Drawable;
26320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.os.Build;
2784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banesimport android.support.annotation.ColorInt;
2840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.DrawableRes;
29320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.annotation.IntDef;
3040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.LayoutRes;
3140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.NonNull;
3240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.Nullable;
3340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.StringRes;
34320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.design.R;
35320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.GravityCompat;
36320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.PagerAdapter;
37320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewCompat;
38320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewPager;
39320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v7.app.ActionBar;
40320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v7.internal.widget.TintManager;
41320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.text.TextUtils;
42320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.util.AttributeSet;
43320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.Gravity;
44320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.LayoutInflater;
45320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.View;
46320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewGroup;
47320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewParent;
48320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityEvent;
49320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityNodeInfo;
50320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.HorizontalScrollView;
51320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.ImageView;
52320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.LinearLayout;
53320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.TextView;
54320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.Toast;
55320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
56320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.Retention;
57320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.RetentionPolicy;
58745636602593f0b8ae508eb10266a3c7c675e746Chris Banesimport java.lang.ref.WeakReference;
59320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.ArrayList;
60320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.Iterator;
61320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
62a4d0ea328b382109435bcfea115d9c41e750af19Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
63bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
64a4d0ea328b382109435bcfea115d9c41e750af19Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
65a4d0ea328b382109435bcfea115d9c41e750af19Chris Banes
66320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/**
679fb154338a62edc2c57dc036895199d6f1769400Chris Banes * TabLayout provides a horizontal layout to display tabs.
689fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
699fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Population of the tabs to display is
70320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can
71320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)}
729fb154338a62edc2c57dc036895199d6f1769400Chris Banes * respectively. To display the tab, you need to add it to the layout via one of the
739fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #addTab(Tab)} methods. For example:
74320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <pre>
75320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * TabLayout tabLayout = ...;
76320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
77320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
78320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
79320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </pre>
80320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
81320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * notified when any tab's selection state has been changed.
82320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <p>
83320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * If you're using a {@link android.support.v4.view.ViewPager} together
84745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * with this layout, you can use {@link #setTabsFromPagerAdapter(PagerAdapter)} which will populate
85745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * the tabs using the given {@link PagerAdapter}'s page titles. You should also use a
86745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * {@link TabLayoutOnPageChangeListener} to forward the scroll and selection changes to this
87745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * layout like so:
88320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <pre>
89320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * ViewPager viewPager = ...;
90320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * TabLayout tabLayout = ...;
91745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(tabLayout));
92320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </pre>
93320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
94320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
95320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */
96320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespublic class TabLayout extends HorizontalScrollView {
97320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
98320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int DEFAULT_HEIGHT = 48; // dps
99320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
100320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
101320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int MOTION_NON_ADJACENT_OFFSET = 24;
102320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
103320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int ANIMATION_DURATION = 300;
104320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
105320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
106320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab
107320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * labels and a larger number of tabs. They are best used for browsing contexts in touch
108320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * interfaces when users don’t need to directly compare the tab labels.
109320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
110320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
111320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
112320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
113320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_SCROLLABLE = 0;
114320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
115320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
116320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs display all tabs concurrently and are best used with content that benefits from
117320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * quick pivots between tabs. The maximum number of tabs is limited by the view’s width.
118320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs have equal width, based on the widest tab label.
119320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
120320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
121320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
122320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
123320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_FIXED = 1;
124320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
125320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
126320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
127320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
128320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED})
129320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
130320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface Mode {}
131320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
132320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
133320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect
134320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * when used with {@link #MODE_FIXED}.
135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
136320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
137320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
138320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
139320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_FILL = 0;
140320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
141320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
142320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to lay out the tabs in the center of the {@link TabLayout}.
143320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
144320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
145320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
146320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
147320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_CENTER = 1;
148320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
151320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
152320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER})
153320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface TabGravity {}
155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
156320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
157320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Callback interface invoked when a tab's selection state changes.
158320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public interface OnTabSelectedListener {
160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
161320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
162320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab enters the selected state.
163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was selected
165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabSelected(Tab tab);
167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab exits the selected state.
170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was unselected
172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabUnselected(Tab tab);
174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab that is already selected is chosen again by the user. Some applications
177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * may use this action to return to the top level of a category.
178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was reselected.
180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabReselected(Tab tab);
182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final ArrayList<Tab> mTabs = new ArrayList<>();
185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private Tab mSelectedTab;
186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final SlidingTabStrip mTabStrip;
188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingStart;
190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingTop;
191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingEnd;
192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingBottom;
193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
19445cbb1d10c865209f373a148556191700d8ea345Chris Banes    private int mTabTextAppearance;
19545cbb1d10c865209f373a148556191700d8ea345Chris Banes    private ColorStateList mTabTextColors;
19645cbb1d10c865209f373a148556191700d8ea345Chris Banes
197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mTabBackgroundResId;
198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mTabMinWidth;
200d67a60744b4a713ca37e219e349b55743a39272aChris Banes    private int mTabMaxWidth = Integer.MAX_VALUE;
201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mRequestedTabMaxWidth;
202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mContentInsetStart;
204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabGravity;
206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mMode;
207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private OnTabSelectedListener mOnTabSelectedListener;
209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private View.OnClickListener mTabClickListener;
210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
211bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes    private ValueAnimatorCompat mScrollAnimator;
212bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes    private ValueAnimatorCompat mIndicatorAnimator;
213bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context) {
215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        this(context, null);
216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
218320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs) {
219320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        this(context, attrs, 0);
220320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
222320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super(context, attrs, defStyleAttr);
224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
225320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Disable the Scroll Bar
226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        setHorizontalScrollBarEnabled(false);
227320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Set us to fill the View port
228320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        setFillViewport(true);
229320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Add the TabStrip
231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip = new SlidingTabStrip(context);
232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                defStyleAttr, R.style.Widget_Design_TabLayout);
236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorHeight(
238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
239320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
241320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                R.style.TextAppearance_Design_Tab);
243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
244320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingStart);
248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
249320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingTop);
250320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
251320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingEnd);
252320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingBottom);
254320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
25545cbb1d10c865209f373a148556191700d8ea345Chris Banes        // Text colors come from the text appearance first
25645cbb1d10c865209f373a148556191700d8ea345Chris Banes        mTabTextColors = loadTextColorFromTextAppearance(mTabTextAppearance);
25745cbb1d10c865209f373a148556191700d8ea345Chris Banes
25845cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
25945cbb1d10c865209f373a148556191700d8ea345Chris Banes            // If we have an explicit text color set, use it instead
26045cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor);
26145cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
26245cbb1d10c865209f373a148556191700d8ea345Chris Banes
263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
26445cbb1d10c865209f373a148556191700d8ea345Chris Banes            // We have an explicit selected text color set, so we need to make merge it with the
26545cbb1d10c865209f373a148556191700d8ea345Chris Banes            // current colors. This is exposed so that developers can use theme attributes to set
26645cbb1d10c865209f373a148556191700d8ea345Chris Banes            // this (theme attrs in ColorStateLists are Lollipop+)
26745cbb1d10c865209f373a148556191700d8ea345Chris Banes            final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
26845cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
269320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
270320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
271320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, 0);
272320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, 0);
273320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
274320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
275320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
276320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
277320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        a.recycle();
278320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
279320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now apply the tab mode and gravity
280320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        applyModeAndGravity();
281320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
282320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
283320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
28484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's color for the currently selected tab.
28584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
28684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param color color to use for the indicator
28784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
28884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorColor(@ColorInt int color) {
28984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorColor(color);
29084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
29184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
29284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
29384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's height for the currently selected tab.
29484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
29584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param height height to use for the indicator in pixels
29684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
29784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorHeight(int height) {
29884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorHeight(height);
29984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
30084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
30184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
303745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
304320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <p>
305320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Calling this method does not update the selected tab, it is only used for drawing purposes.
306ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     *
307ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param position current scroll position
308ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
309ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param updateSelectedText Whether to update the text's selected state.
310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
311ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes    public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
312bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
313320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
314320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
315320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (position < 0 || position >= mTabStrip.getChildCount()) {
316320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
317320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
318320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
319320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Set the indicator position and update the scroll to match
320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
321320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        scrollTo(calculateScrollXForTab(position, positionOffset), 0);
322320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
323320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Update the 'selected state' view as we scroll
324ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        if (updateSelectedText) {
325ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes            setSelectedTabView(Math.round(position + positionOffset));
326ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        }
327320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
328320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
32909db0f00a693704eb747027def4279362c51894eChris Banes    private float getScrollPosition() {
33009db0f00a693704eb747027def4279362c51894eChris Banes        return mTabStrip.getIndicatorPosition();
33109db0f00a693704eb747027def4279362c51894eChris Banes    }
33209db0f00a693704eb747027def4279362c51894eChris Banes
333320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
334320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
335320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
336320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
337320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
338320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
33940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab) {
340320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, mTabs.isEmpty());
341320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
342320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
343320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
344320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
345320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
347320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
348320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
349320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
35040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position) {
351320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, position, mTabs.isEmpty());
352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
354320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
355320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
356320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
357320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
358320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
359320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
36040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, boolean setSelected) {
361320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
362320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
363320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
365320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, setSelected);
366320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, mTabs.size());
367320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
368320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
369320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
370320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
371320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
373320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
374320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
375320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
376320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
377320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
378320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
37940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
380320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
382320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
383320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
384320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, position, setSelected);
385320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, position);
386320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
387320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
388320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
390320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
392745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will
393745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * handle switching to and from tabs.
394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param onTabSelectedListener Listener to handle tab selection events
396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mOnTabSelectedListener = onTabSelectedListener;
399320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
400320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
401320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
402320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Create and return a new {@link Tab}. You need to manually add this using
403320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * {@link #addTab(Tab)} or a related method.
404320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
405320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return A new Tab
406320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #addTab(Tab)
407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
40840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @NonNull
409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab newTab() {
410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return new Tab(this);
411320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
412320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the number of tabs currently registered with the action bar.
415320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
416320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return Tab count
417320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
418320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabCount() {
419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.size();
420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
421320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
423320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the tab at the specified index.
424320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
42540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
426320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab getTabAt(int index) {
427320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.get(index);
428320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
429320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
430320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
4311a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * Returns the position of the current selected tab.
4321a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     *
4331a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * @return selected tab position, or {@code -1} if there isn't a selected tab.
4341a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     */
4351a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    public int getSelectedTabPosition() {
4361a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
4371a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    }
4381a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
4391a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    /**
440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
443320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to remove
444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
445320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTab(Tab tab) {
446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
447320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab does not belong to this TabLayout.");
448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
449320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
450320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabAt(tab.getPosition());
451320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
452320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position Position of the tab to remove
458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTabAt(int position) {
460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabViewAt(position);
462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        Tab removedTab = mTabs.remove(position);
464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (removedTab != null) {
465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            removedTab.setPosition(Tab.INVALID_POSITION);
466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int newTabCount = mTabs.size();
469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position; i < newTabCount; i++) {
470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (selectedTabPosition == position) {
474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
475320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
476320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
477320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
478320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
479320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove all tabs from the action bar and deselect the current tab.
480320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
481320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeAllTabs() {
482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Remove all the views
483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.removeAllViews();
484320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
485320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (Iterator<Tab> i = mTabs.iterator(); i.hasNext(); ) {
486320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            Tab tab = i.next();
487320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.setPosition(Tab.INVALID_POSITION);
488320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            i.remove();
489320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
490bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
491bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mSelectedTab = null;
492320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
493320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
494320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
495320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the behavior mode for the Tabs in this layout. The valid input options are:
496320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <ul>
497320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used
498320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * with content that benefits from quick pivots between tabs.</li>
499320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
500320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and can contain longer tab labels and a larger number of tabs. They are best used for
501320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * browsing contexts in touch interfaces when users don’t need to directly compare the tab
502745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li>
503320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * </ul>
504320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
505320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
506320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
507320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabMode(@Mode int mode) {
508320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mode != mMode) {
509320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mMode = mode;
510320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
512320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
513320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
514320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
515320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the current mode used by this {@link TabLayout}.
516320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
517320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
518320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
519320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Mode
520320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabMode() {
521320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mMode;
522320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
523320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
524320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
525320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the gravity to use when laying out the tabs.
526320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
527320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
528320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabGravity(@TabGravity int gravity) {
530320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mTabGravity != gravity) {
531320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabGravity = gravity;
532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
533320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
534320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
535320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
536320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
537320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * The current gravity used for laying out tabs.
538320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
539320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
540320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
541320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @TabGravity
542320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabGravity() {
543320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabGravity;
544320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
545320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
546320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
54745cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
548320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
54940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void setTabTextColors(@Nullable ColorStateList textColor) {
55045cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (mTabTextColors != textColor) {
55145cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = textColor;
55245cbb1d10c865209f373a148556191700d8ea345Chris Banes            updateAllTabs();
553320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
554320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
555320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
556320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
55745cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Gets the text colors for the different states (normal, selected) used for the tabs.
558320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
55940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
56045cbb1d10c865209f373a148556191700d8ea345Chris Banes    public ColorStateList getTabTextColors() {
56145cbb1d10c865209f373a148556191700d8ea345Chris Banes        return mTabTextColors;
56245cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
56345cbb1d10c865209f373a148556191700d8ea345Chris Banes
56445cbb1d10c865209f373a148556191700d8ea345Chris Banes    /**
56545cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
56645cbb1d10c865209f373a148556191700d8ea345Chris Banes     */
56745cbb1d10c865209f373a148556191700d8ea345Chris Banes    public void setTabTextColors(int normalColor, int selectedColor) {
56845cbb1d10c865209f373a148556191700d8ea345Chris Banes        setTabTextColors(createColorStateList(normalColor, selectedColor));
56945cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
57045cbb1d10c865209f373a148556191700d8ea345Chris Banes
571745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
572745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
573745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
574745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <p>This method will:
575745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <ul>
576745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *     <li>Add a {@link ViewPager.OnPageChangeListener} that will forward events to
577745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *     this TabLayout.</li>
578745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *     <li>Populate the TabLayout's tabs from the ViewPager's {@link PagerAdapter}.</li>
579745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *     <li>Set our {@link TabLayout.OnTabSelectedListener} which will forward
580745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *     selected events to the ViewPager</li>
581745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * </ul>
582745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * </p>
583745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
584745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * @see #setTabsFromPagerAdapter(PagerAdapter)
585745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * @see TabLayoutOnPageChangeListener
586745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * @see ViewPagerOnTabSelectedListener
587745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
58840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void setupWithViewPager(@NonNull ViewPager viewPager) {
589745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        final PagerAdapter adapter = viewPager.getAdapter();
590745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        if (adapter == null) {
591745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
592745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
593745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
594745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        // First we'll add Tabs, using the adapter's page titles
595745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        setTabsFromPagerAdapter(adapter);
596745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
597745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        // Now we'll add our page change listener to the ViewPager
598745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(this));
599745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
600745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        // Now we'll add a tab selected listener to set ViewPager's current item
601745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
602112f648090b48c0d00a51983baf148dbbc800abbChris Banes
603112f648090b48c0d00a51983baf148dbbc800abbChris Banes        // Make sure we reflect the currently set ViewPager item
60409db0f00a693704eb747027def4279362c51894eChris Banes        if (adapter.getCount() > 0) {
60509db0f00a693704eb747027def4279362c51894eChris Banes            final int curItem = viewPager.getCurrentItem();
60609db0f00a693704eb747027def4279362c51894eChris Banes            if (getSelectedTabPosition() != curItem) {
60709db0f00a693704eb747027def4279362c51894eChris Banes                selectTab(getTabAt(curItem));
60809db0f00a693704eb747027def4279362c51894eChris Banes            }
609112f648090b48c0d00a51983baf148dbbc800abbChris Banes        }
610745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
611745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
612745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
613745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * Populate our tab content from the given {@link PagerAdapter}.
614745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <p>
615745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * Any existing tabs will be removed first. Each tab will have it's text set to the value
616745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * returned from {@link PagerAdapter#getPageTitle(int)}
617745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * </p>
618745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
619745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * @param adapter the adapter to populate from
620745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
62140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void setTabsFromPagerAdapter(@NonNull PagerAdapter adapter) {
622745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        removeAllTabs();
623745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        for (int i = 0, count = adapter.getCount(); i < count; i++) {
624745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            addTab(newTab().setText(adapter.getPageTitle(i)));
625745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
626745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
627745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
62845cbb1d10c865209f373a148556191700d8ea345Chris Banes    private void updateAllTabs() {
62945cbb1d10c865209f373a148556191700d8ea345Chris Banes        for (int i = 0, z = mTabStrip.getChildCount(); i < z; i++) {
63045cbb1d10c865209f373a148556191700d8ea345Chris Banes            updateTab(i);
63145cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
632320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
633320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
634320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private TabView createTabView(Tab tab) {
635320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final TabView tabView = new TabView(getContext(), tab);
636320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tabView.setFocusable(true);
637320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
638320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mTabClickListener == null) {
639320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabClickListener = new View.OnClickListener() {
640320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                @Override
641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                public void onClick(View view) {
642320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    TabView tabView = (TabView) view;
643320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    tabView.getTab().select();
644320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
645320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            };
646320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
647320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tabView.setOnClickListener(mTabClickListener);
648320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return tabView;
649320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
650320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
651320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void configureTab(Tab tab, int position) {
652320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tab.setPosition(position);
653320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabs.add(position, tab);
654320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
655320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int count = mTabs.size();
656320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position + 1; i < count; i++) {
657320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
658320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
659320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
660320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
661320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void updateTab(int position) {
662320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final TabView view = (TabView) mTabStrip.getChildAt(position);
663320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (view != null) {
664320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            view.update();
665320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
666320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
667320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, boolean setSelected) {
669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final TabView tabView = createTabView(tab);
670320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, createLayoutParamsForTabs());
671320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
672320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
673320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
674320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
676320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, int position, boolean setSelected) {
677320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final TabView tabView = createTabView(tab);
678320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
679320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
680320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
681320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
682320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
683320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
684320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
685320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
686320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
687320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        updateTabViewLayoutParams(lp);
688320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return lp;
689320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
690320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
691320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
692320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
693320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = 0;
694320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 1;
695320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
696320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
697320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 0;
698320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
699320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
700320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
701320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int dpToPx(int dps) {
702320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return Math.round(getResources().getDisplayMetrics().density * dps);
703320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
704320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
705320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Override
706320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
707320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // If we have a MeasureSpec which allows us to decide our height, try and use the default
708320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // height
709bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        final int idealHeight = dpToPx(DEFAULT_HEIGHT) + getPaddingTop() + getPaddingBottom();
710320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (MeasureSpec.getMode(heightMeasureSpec)) {
711320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.AT_MOST:
712320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
713bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
714320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        MeasureSpec.EXACTLY);
715320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
716320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.UNSPECIFIED:
717bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
718320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
719320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
720320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
721320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now super measure itself using the (possibly) modified height spec
722320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
723320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
724320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_FIXED && getChildCount() == 1) {
725320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're in fixed mode then we need to make the tab strip is the same width as us
726320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // so we don't scroll
727320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View child = getChildAt(0);
728320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int width = getMeasuredWidth();
729320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
730320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (child.getMeasuredWidth() > width) {
731320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the child is wider than us, re-measure it with a widthSpec set to exact our
732320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // measure width
733320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
734320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        + getPaddingBottom(), child.getLayoutParams().height);
735320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
736320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
737320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
738320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
739320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
740320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now update the tab max width. We do it here as the default tab min width is
741320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // layout width - 56dp
742320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        int maxTabWidth = mRequestedTabMaxWidth;
743320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int defaultTabMaxWidth = getMeasuredWidth() - dpToPx(TAB_MIN_WIDTH_MARGIN);
744320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (maxTabWidth == 0 || maxTabWidth > defaultTabMaxWidth) {
745320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If the request tab max width is 0, or larger than our default, use the default
746320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            maxTabWidth = defaultTabMaxWidth;
747320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
748d67a60744b4a713ca37e219e349b55743a39272aChris Banes
749d67a60744b4a713ca37e219e349b55743a39272aChris Banes        if (mTabMaxWidth != maxTabWidth) {
750d67a60744b4a713ca37e219e349b55743a39272aChris Banes            // If the tab max width has changed, re-measure
751d67a60744b4a713ca37e219e349b55743a39272aChris Banes            mTabMaxWidth = maxTabWidth;
752d67a60744b4a713ca37e219e349b55743a39272aChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
753d67a60744b4a713ca37e219e349b55743a39272aChris Banes        }
754320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
755320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
756320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void removeTabViewAt(int position) {
757320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.removeViewAt(position);
758320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        requestLayout();
759320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
760320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
761320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void animateToTab(int newPosition) {
762320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (newPosition == Tab.INVALID_POSITION) {
763320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
764320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
7657c71281dc4879e7889c65283aabaeed68521fb14Alan Jeon
766bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
767bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                || mTabStrip.childrenNeedLayout()) {
768320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we don't have a window token, or we haven't been laid out yet just draw the new
769320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // position now
770ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes            setScrollPosition(newPosition, 0f, true);
771320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
772320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
773320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
774320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int startScrollX = getScrollX();
775320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int targetScrollX = calculateScrollXForTab(newPosition, 0);
776320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
777320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (startScrollX != targetScrollX) {
778bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            if (mScrollAnimator == null) {
779bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator = ViewUtils.createAnimator();
780bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
781bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setDuration(ANIMATION_DURATION);
782bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
783bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    @Override
784bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
785bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        scrollTo(animator.getAnimatedIntValue(), 0);
786bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    }
787bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                });
788bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
789bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
790bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.setIntValues(startScrollX, targetScrollX);
791bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.start();
792320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
793320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
794320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now animate the indicator
795bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
796320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
797320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
798320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void setSelectedTabView(int position) {
799320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int tabCount = mTabStrip.getChildCount();
80009db0f00a693704eb747027def4279362c51894eChris Banes        if (position < tabCount && !mTabStrip.getChildAt(position).isSelected()) {
80109db0f00a693704eb747027def4279362c51894eChris Banes            for (int i = 0; i < tabCount; i++) {
80209db0f00a693704eb747027def4279362c51894eChris Banes                final View child = mTabStrip.getChildAt(i);
80309db0f00a693704eb747027def4279362c51894eChris Banes                child.setSelected(i == position);
80409db0f00a693704eb747027def4279362c51894eChris Banes            }
805320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
806320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
807320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
808320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    void selectTab(Tab tab) {
80915ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes        selectTab(tab, true);
81015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes    }
81115ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes
81215ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes    void selectTab(Tab tab, boolean updateIndicator) {
813320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mSelectedTab == tab) {
814320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null) {
815320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mOnTabSelectedListener != null) {
816320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mOnTabSelectedListener.onTabReselected(mSelectedTab);
817320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
818320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                animateToTab(tab.getPosition());
819320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
820320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
821320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
822320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setSelectedTabView(newPosition);
82315ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes            if (updateIndicator) {
82415ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
82515ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                        && newPosition != Tab.INVALID_POSITION) {
82615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    // If we don't currently have a tab, just draw the indicator
82715ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    setScrollPosition(newPosition, 0f, true);
82815ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                } else {
82915ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    animateToTab(newPosition);
83015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                }
831320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
832320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null && mOnTabSelectedListener != null) {
833320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mOnTabSelectedListener.onTabUnselected(mSelectedTab);
834320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
835320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedTab = tab;
836320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null && mOnTabSelectedListener != null) {
837320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mOnTabSelectedListener.onTabSelected(mSelectedTab);
838320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
839320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
840320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
841320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
842320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int calculateScrollXForTab(int position, float positionOffset) {
843320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
844320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedChild = mTabStrip.getChildAt(position);
845320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View nextChild = position + 1 < mTabStrip.getChildCount()
846320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    ? mTabStrip.getChildAt(position + 1)
847320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    : null;
848320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
849320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
850320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
851bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return selectedChild.getLeft()
852bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f))
853bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + (selectedChild.getWidth() / 2)
854bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    - (getWidth() / 2);
855320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
856320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return 0;
857320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
858320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
859320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void applyModeAndGravity() {
860320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        int paddingStart = 0;
861320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
862320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're scrollable, or fixed at start, inset using padding
863320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
864320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
865320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
866320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
867320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (mMode) {
868320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_FIXED:
869320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
870320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
871320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_SCROLLABLE:
872320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(GravityCompat.START);
873320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
874320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
875320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
876320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        updateTabViewsLayoutParams();
877320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
878320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
879320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void updateTabViewsLayoutParams() {
880320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = 0; i < mTabStrip.getChildCount(); i++) {
881320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            View child = mTabStrip.getChildAt(i);
882320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
883320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            child.requestLayout();
884320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
885320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
886320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
887320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
888320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * A tab in this layout. Instances can be created via {@link #newTab()}.
889320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
890320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final class Tab {
891320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
892320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
893320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * An invalid position for a tab.
894320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
895320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getPosition()
896320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
897320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public static final int INVALID_POSITION = -1;
898320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
899320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Object mTag;
900320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Drawable mIcon;
901320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mText;
902320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mContentDesc;
903320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mPosition = INVALID_POSITION;
904320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
905320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
906320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final TabLayout mParent;
907320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
908320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        Tab(TabLayout parent) {
909320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mParent = parent;
910320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
911320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
912320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
913320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return This Tab's tag object.
914320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
91540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
916320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Object getTag() {
917320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTag;
918320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
919320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
920320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
921320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Give this Tab an arbitrary object to hold for later use.
922320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
923320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tag Object to store
924320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
925320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
92640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
92740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setTag(@Nullable Object tag) {
928320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTag = tag;
929320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
930320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
931320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
93221f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes
93321f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        /**
93421f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * Returns the custom view used for this tab.
93521f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         *
93621f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(View)
93721f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(int)
93821f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         */
93921f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        @Nullable
94021f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        public View getCustomView() {
941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mCustomView;
942320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
943320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
944320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
94540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
94640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
94740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the provided view contains a {@link TextView} with an ID of
94840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
94940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
95040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
95140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
95240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
953320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
954320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param view Custom view to be used as a tab.
955320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
956320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
95740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
95840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setCustomView(@Nullable View view) {
959320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mCustomView = view;
960320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mPosition >= 0) {
961320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mParent.updateTab(mPosition);
962320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
963320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
964320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
965320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
966320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
96740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
96840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
96940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the inflated layout contains a {@link TextView} with an ID of
97040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
97140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
97240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
97340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
97440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
975320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
976320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param layoutResId A layout resource to inflate and use as a custom tab view
977320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
978320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
97940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
98040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setCustomView(@LayoutRes int layoutResId) {
981320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setCustomView(
982320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    LayoutInflater.from(mParent.getContext()).inflate(layoutResId, null));
983320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
984320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
985320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
986320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the icon associated with this tab.
987320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's icon
989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
99040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Drawable getIcon() {
992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mIcon;
993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the current position of this tab in the action bar.
997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the action bar.
1000320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1001320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public int getPosition() {
1002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mPosition;
1003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setPosition(int position) {
1006320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mPosition = position;
1007320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1008320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1009320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1010320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the text of this tab.
1011320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1012320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's text
1013320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
101440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1015320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getText() {
1016320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mText;
1017320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1018320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1019320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1020320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1021320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1022320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param icon The drawable to use as an icon
1023320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1024320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
102540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
102640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@Nullable Drawable icon) {
1027320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mIcon = icon;
1028320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mPosition >= 0) {
1029320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mParent.updateTab(mPosition);
1030320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1031320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1032320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1033320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1034320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1035320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1036320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1037320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the icon that should be displayed
1038320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1039320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
104040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
104140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@DrawableRes int resId) {
1042320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setIcon(TintManager.getDrawable(mParent.getContext(), resId));
1043320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1044320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1045320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1046320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1047320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1048320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1049320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param text The text to display
1050320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1051320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
105240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
105340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@Nullable CharSequence text) {
1054320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mText = text;
1055320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mPosition >= 0) {
1056320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mParent.updateTab(mPosition);
1057320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1058320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1059320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1060320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1061320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1062320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1063320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1064320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1065320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the text that should be displayed
1066320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1067320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
106840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
106940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@StringRes int resId) {
1070320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setText(mParent.getResources().getText(resId));
1071320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1072320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1073320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1074320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Select this tab. Only valid if the tab has been added to the action bar.
1075320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1076320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void select() {
1077320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mParent.selectTab(this);
1078320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1079320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1080320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
10811a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         * Returns true if this tab is currently selected.
10821a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         */
10831a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        public boolean isSelected() {
10841a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes            return mParent.getSelectedTabPosition() == mPosition;
10851a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        }
10861a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
10871a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        /**
1088320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1089320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1090320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1091320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the description text
1092320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1093320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1094320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1095320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
109640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
109740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@StringRes int resId) {
1098320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setContentDescription(mParent.getResources().getText(resId));
1099320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1100320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1101320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1102320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1103320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1104320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1105320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param contentDesc Description of this tab's content
1106320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1107320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1108320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1109320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
111040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
111140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@Nullable CharSequence contentDesc) {
1112320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mContentDesc = contentDesc;
1113320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mPosition >= 0) {
1114320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mParent.updateTab(mPosition);
1115320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1116320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1117320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1118320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1119320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1120320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Gets a brief description of this tab's content for use in accessibility support.
1121320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1122320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Description of this tab's content
1123320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1124320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1125320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
112640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1127320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getContentDescription() {
1128320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mContentDesc;
1129320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1130320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1131320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1132320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    class TabView extends LinearLayout implements OnLongClickListener {
1133320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final Tab mTab;
1134320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private TextView mTextView;
1135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private ImageView mIconView;
113640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
1137320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
113840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private TextView mCustomTextView;
113940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private ImageView mCustomIconView;
1140320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1141320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public TabView(Context context, Tab tab) {
1142320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1143320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTab = tab;
1144320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mTabBackgroundResId != 0) {
1145320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                setBackgroundDrawable(TintManager.getDrawable(context, mTabBackgroundResId));
1146320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1147320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
1148320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabPaddingEnd, mTabPaddingBottom);
1149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setGravity(Gravity.CENTER);
1150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            update();
1151320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1152320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1153320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void setSelected(boolean selected) {
1155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean changed = (isSelected() != selected);
1156320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.setSelected(selected);
1157320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (changed && selected) {
1158320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1161320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setSelected(selected);
1162320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setSelected(selected);
1165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityEvent(event);
1173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            event.setClassName(ActionBar.Tab.class.getName());
1175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityNodeInfo(info);
1181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            info.setClassName(ActionBar.Tab.class.getName());
1183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
118935ee849e74452227047987c4eb2355b8a61b0505Chris Banes            final int measuredWidth = getMeasuredWidth();
119035ee849e74452227047987c4eb2355b8a61b0505Chris Banes            if (measuredWidth < mTabMinWidth || measuredWidth > mTabMaxWidth) {
119135ee849e74452227047987c4eb2355b8a61b0505Chris Banes                // Re-measure if we are outside our min or max width
119235ee849e74452227047987c4eb2355b8a61b0505Chris Banes                widthMeasureSpec = MeasureSpec.makeMeasureSpec(
119335ee849e74452227047987c4eb2355b8a61b0505Chris Banes                        MathUtils.constrain(measuredWidth, mTabMinWidth, mTabMaxWidth),
119435ee849e74452227047987c4eb2355b8a61b0505Chris Banes                        MeasureSpec.EXACTLY);
119535ee849e74452227047987c4eb2355b8a61b0505Chris Banes                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final void update() {
1200320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Tab tab = mTab;
1201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View custom = tab.getCustomView();
1202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (custom != null) {
1203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final ViewParent customParent = custom.getParent();
1204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (customParent != this) {
1205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (customParent != null) {
1206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        ((ViewGroup) customParent).removeView(custom);
1207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    addView(custom);
1209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mCustomView = custom;
1211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setVisibility(GONE);
1213320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setVisibility(GONE);
1216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setImageDrawable(null);
1217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
121840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
121940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
122040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
1221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
122240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // We do not have a custom view. Remove one if it already exists
1223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mCustomView != null) {
1224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    removeView(mCustomView);
1225320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mCustomView = null;
1226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
122740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = null;
122840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = null;
122940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
123140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (mCustomView == null) {
123240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // If there isn't a custom view, we'll us our own in-built layouts
123340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mIconView == null) {
123440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    ImageView iconView = (ImageView) LayoutInflater.from(getContext())
1235a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_icon, this, false);
123640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(iconView, 0);
123740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mIconView = iconView;
1238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
123940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTextView == null) {
124040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    TextView textView = (TextView) LayoutInflater.from(getContext())
1241a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_text, this, false);
124240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(textView);
124340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView = textView;
124440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
124540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mTextView.setTextAppearance(getContext(), mTabTextAppearance);
124640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTabTextColors != null) {
124740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView.setTextColor(mTabTextColors);
1248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
124940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                updateTextAndIcon(tab, mTextView, mIconView);
125040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
125140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // Else, we'll see if there is a TextView or ImageView present and update them
125240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mCustomTextView != null || mCustomIconView != null) {
125340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    updateTextAndIcon(tab, mCustomTextView, mCustomIconView);
125440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
125540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
125640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        }
1257320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
125840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private void updateTextAndIcon(Tab tab, TextView textView, ImageView iconView) {
125940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            final Drawable icon = tab.getIcon();
126040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            final CharSequence text = tab.getText();
126140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
126240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (iconView != null) {
126340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (icon != null) {
126440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(icon);
126540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(VISIBLE);
126640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
126740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                } else {
126840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(GONE);
126940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(null);
1270320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
127140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                iconView.setContentDescription(tab.getContentDescription());
127240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1273320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
127440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            final boolean hasText = !TextUtils.isEmpty(text);
127540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (textView != null) {
127640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (hasText) {
127740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(text);
127840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setContentDescription(tab.getContentDescription());
127940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(VISIBLE);
128040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
1281320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
128240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(GONE);
128340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(null);
1284320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1285320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
128640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
128740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
128840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(this);
128940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
129040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(null);
129140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setLongClickable(false);
129240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1293320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1294320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1295320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1296320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public boolean onLongClick(View v) {
1297320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int[] screenPos = new int[2];
1298320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            getLocationOnScreen(screenPos);
1299320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Context context = getContext();
1301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int width = getWidth();
1302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int height = getHeight();
1303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
1304320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1305320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
1306320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    Toast.LENGTH_SHORT);
1307320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // Show under the tab
1308320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
1309320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    (screenPos[0] + width / 2) - screenWidth / 2, height);
1310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1311320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            cheatSheet.show();
1312320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return true;
1313320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1314320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1315320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Tab getTab() {
1316320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTab;
1317320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1318320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1319320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private class SlidingTabStrip extends LinearLayout {
1321320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedIndicatorHeight;
1322320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final Paint mSelectedIndicatorPaint;
1323320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1324320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedPosition = -1;
1325320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private float mSelectionOffset;
1326320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1327320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorLeft = -1;
1328320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorRight = -1;
1329320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1330320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        SlidingTabStrip(Context context) {
1331320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1332320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setWillNotDraw(false);
1333320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedIndicatorPaint = new Paint();
1334320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1335320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1336320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorColor(int color) {
133709db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorPaint.getColor() != color) {
133809db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorPaint.setColor(color);
133909db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
134009db0f00a693704eb747027def4279362c51894eChris Banes            }
1341320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1342320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1343320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorHeight(int height) {
134409db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorHeight != height) {
134509db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorHeight = height;
134609db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
134709db0f00a693704eb747027def4279362c51894eChris Banes            }
1348320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1349320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1350bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        boolean childrenNeedLayout() {
1351bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            for (int i = 0, z = getChildCount(); i < z; i++) {
1352bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                final View child = getChildAt(i);
1353bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                if (child.getWidth() <= 0) {
1354bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    return true;
1355bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                }
1356bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
1357bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return false;
1358bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        }
1359bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
1360320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
1361320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedPosition = position;
1362320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectionOffset = positionOffset;
1363320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateIndicatorPosition();
1364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1365320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
136609db0f00a693704eb747027def4279362c51894eChris Banes        float getIndicatorPosition() {
136709db0f00a693704eb747027def4279362c51894eChris Banes            return mSelectedPosition + mSelectionOffset;
136809db0f00a693704eb747027def4279362c51894eChris Banes        }
136909db0f00a693704eb747027def4279362c51894eChris Banes
1370320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1371320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1373320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1374320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
1375320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // HorizontalScrollView will first measure use with UNSPECIFIED, and then with
1376320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // EXACTLY. Ignore the first call since anything we do will be overwritten anyway
1377320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                return;
1378320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1379320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1380320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
1381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int count = getChildCount();
1382320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1383320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1384320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1385320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // First we'll find the largest tab
1386320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int largestTabWidth = 0;
1387320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                for (int i = 0, z = count; i < z; i++) {
1388320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    final View child = getChildAt(i);
1389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    child.measure(unspecifiedSpec, heightMeasureSpec);
1390320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
1391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1393320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth <= 0) {
1394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If we don't have a largest child yet, skip until the next measure pass
1395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    return;
1396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
1399320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
1400320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs fit within our width minus gutters, we will set all tabs to have
1401320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // the same width
1402320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    for (int i = 0; i < count; i++) {
1403320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        final View child = getChildAt(i);
1404320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        final LinearLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
1405320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        lp.width = largestTabWidth;
1406320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        lp.weight = 0;
1407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1408320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs will wrap to be larger than the width minus gutters, we need
1410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // to switch to GRAVITY_FILL
1411320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabGravity = GRAVITY_FILL;
1412320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    updateTabViewsLayoutParams();
1413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1415320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // Now re-measure after our changes
1416320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1417320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1418320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1421320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onLayout(changed, l, t, r, b);
1423bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            // If we've been layed out, update the indicator position
1424bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            updateIndicatorPosition();
1425320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1426320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1427320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void updateIndicatorPosition() {
1428320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedTitle = getChildAt(mSelectedPosition);
1429320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            int left, right;
1430320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1431320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (selectedTitle != null && selectedTitle.getWidth() > 0) {
1432320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = selectedTitle.getLeft();
1433320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                right = selectedTitle.getRight();
1434320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1435320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
1436320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // Draw the selection partway between the tabs
1437320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    View nextTitle = getChildAt(mSelectedPosition + 1);
1438320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    left = (int) (mSelectionOffset * nextTitle.getLeft() +
1439320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * left);
1440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    right = (int) (mSelectionOffset * nextTitle.getRight() +
1441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * right);
1442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1443320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = right = -1;
1445320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1447320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setIndicatorPosition(left, right);
1448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1449320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1450320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void setIndicatorPosition(int left, int right) {
1451320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (left != mIndicatorLeft || right != mIndicatorRight) {
1452320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the indicator's left/right has changed, invalidate
1453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorLeft = left;
1454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorRight = right;
1455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                ViewCompat.postInvalidateOnAnimation(this);
1456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void animateIndicatorToPosition(final int position, int duration) {
1460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean isRtl = ViewCompat.getLayoutDirection(this)
1461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    == ViewCompat.LAYOUT_DIRECTION_RTL;
1462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View targetView = getChildAt(position);
1464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetLeft = targetView.getLeft();
1465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetRight = targetView.getRight();
1466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startLeft;
1467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startRight;
1468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (Math.abs(position - mSelectedPosition) <= 1) {
1470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the views are adjacent, we'll animate from edge-to-edge
1471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startLeft = mIndicatorLeft;
1472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startRight = mIndicatorRight;
1473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // Else, we'll just grow from the nearest edge
1475320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
1476320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (position < mSelectedPosition) {
1477320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going end-to-start
1478320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1479320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
1480320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
1481320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1484320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going start-to-end
1485320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1486320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1487320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
1488320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
1489320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1490320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1491320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1492320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1493320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (startLeft != targetLeft || startRight != targetRight) {
1494bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                ValueAnimatorCompat animator = mIndicatorAnimator = ViewUtils.createAnimator();
1495f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
1496f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setDuration(duration);
1497f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setFloatValues(0, 1);
1498f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
1499320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
1500f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
1501f44e88b81821e61a65886cc7027617ca8995070bChris Banes                        final float fraction = animator.getAnimatedFraction();
1502320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        setIndicatorPosition(
1503f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startLeft, targetLeft, fraction),
1504f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startRight, targetRight, fraction));
1505320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1506f44e88b81821e61a65886cc7027617ca8995070bChris Banes                });
1507f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
1508320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
1509f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationEnd(ValueAnimatorCompat animator) {
1510320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectedPosition = position;
1511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectionOffset = 0f;
1512320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1513320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1514320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
1515f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationCancel(ValueAnimatorCompat animator) {
1516f44e88b81821e61a65886cc7027617ca8995070bChris Banes                        mSelectedPosition = position;
1517f44e88b81821e61a65886cc7027617ca8995070bChris Banes                        mSelectionOffset = 0f;
1518320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1519320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                });
1520f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.start();
1521320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1522320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1523320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1524320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1525442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes        public void draw(Canvas canvas) {
1526442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes            super.draw(canvas);
1527442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes
1528320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // Thick colored underline below the current selection
1529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
1530320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
1531320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
1532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1533320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1534320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1535320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
153645cbb1d10c865209f373a148556191700d8ea345Chris Banes    private static ColorStateList createColorStateList(int defaultColor, int selectedColor) {
153745cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[][] states = new int[2][];
153845cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[] colors = new int[2];
153945cbb1d10c865209f373a148556191700d8ea345Chris Banes        int i = 0;
154045cbb1d10c865209f373a148556191700d8ea345Chris Banes
154145cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = SELECTED_STATE_SET;
154245cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = selectedColor;
154345cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
154445cbb1d10c865209f373a148556191700d8ea345Chris Banes
154545cbb1d10c865209f373a148556191700d8ea345Chris Banes        // Default enabled state
154645cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = EMPTY_STATE_SET;
154745cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = defaultColor;
154845cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
154945cbb1d10c865209f373a148556191700d8ea345Chris Banes
155045cbb1d10c865209f373a148556191700d8ea345Chris Banes        return new ColorStateList(states, colors);
155145cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
155245cbb1d10c865209f373a148556191700d8ea345Chris Banes
155345cbb1d10c865209f373a148556191700d8ea345Chris Banes    private ColorStateList loadTextColorFromTextAppearance(int textAppearanceResId) {
155445cbb1d10c865209f373a148556191700d8ea345Chris Banes        TypedArray a = getContext().obtainStyledAttributes(textAppearanceResId,
155545cbb1d10c865209f373a148556191700d8ea345Chris Banes                R.styleable.TextAppearance);
155645cbb1d10c865209f373a148556191700d8ea345Chris Banes        try {
155745cbb1d10c865209f373a148556191700d8ea345Chris Banes            return a.getColorStateList(R.styleable.TextAppearance_android_textColor);
155845cbb1d10c865209f373a148556191700d8ea345Chris Banes        } finally {
155945cbb1d10c865209f373a148556191700d8ea345Chris Banes            a.recycle();
156045cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
156145cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
156245cbb1d10c865209f373a148556191700d8ea345Chris Banes
1563745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
1564745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link ViewPager.OnPageChangeListener} class which contains the
1565745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * necessary calls back to the provided {@link TabLayout} so that the tab position is
1566745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * kept in sync.
1567745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
1568745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <p>This class stores the provided TabLayout weakly, meaning that you can use
1569745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
1570745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
1571745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * not cause a leak.
1572745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
1573745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
1574745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final WeakReference<TabLayout> mTabLayoutRef;
157509db0f00a693704eb747027def4279362c51894eChris Banes        private int mPendingSelection = -1;
1576745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private int mScrollState;
1577745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1578745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
1579745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mTabLayoutRef = new WeakReference<>(tabLayout);
1580745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1581745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1582745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1583745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onPageScrollStateChanged(int state) {
1584745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mScrollState = state;
1585745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1586745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1587745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
158809db0f00a693704eb747027def4279362c51894eChris Banes        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
1589745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            final TabLayout tabLayout = mTabLayoutRef.get();
1590745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            if (tabLayout != null) {
159109db0f00a693704eb747027def4279362c51894eChris Banes                if (mPendingSelection == -1 || tabLayout.getScrollPosition() != mPendingSelection) {
159209db0f00a693704eb747027def4279362c51894eChris Banes                    // If we don't have a pending selection, or the drawn position is not already
159309db0f00a693704eb747027def4279362c51894eChris Banes                    // at the selection, move the draw position
159409db0f00a693704eb747027def4279362c51894eChris Banes                    tabLayout.setScrollPosition(position, positionOffset, true);
159509db0f00a693704eb747027def4279362c51894eChris Banes                }
159609db0f00a693704eb747027def4279362c51894eChris Banes                if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
159709db0f00a693704eb747027def4279362c51894eChris Banes                    // As we're now idle, if we have a pending selection select it now
159809db0f00a693704eb747027def4279362c51894eChris Banes                    if (mPendingSelection != -1) {
159909db0f00a693704eb747027def4279362c51894eChris Banes                        tabLayout.selectTab(tabLayout.getTabAt(mPendingSelection));
160009db0f00a693704eb747027def4279362c51894eChris Banes                        mPendingSelection = -1;
160109db0f00a693704eb747027def4279362c51894eChris Banes                    }
160209db0f00a693704eb747027def4279362c51894eChris Banes                }
1603745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            }
1604745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1605745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1606745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1607745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onPageSelected(int position) {
160809db0f00a693704eb747027def4279362c51894eChris Banes            // This call is made before onPageScrolled() which can lead to a jerk if we just
160909db0f00a693704eb747027def4279362c51894eChris Banes            // selected the tab now. So we'll keep the position, and set it when we're idle again
161009db0f00a693704eb747027def4279362c51894eChris Banes            mPendingSelection = position;
1611745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1612745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
1613745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1614745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
1615745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back
1616745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * to the provided {@link ViewPager} so that the tab position is kept in sync.
1617745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
1618745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
1619745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final ViewPager mViewPager;
1620745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1621745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
1622745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager = viewPager;
1623745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1624745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1625745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1626745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabSelected(TabLayout.Tab tab) {
1627745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager.setCurrentItem(tab.getPosition());
1628745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1629745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1630745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1631745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabUnselected(TabLayout.Tab tab) {
1632745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
1633745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1634745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1635745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1636745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabReselected(TabLayout.Tab tab) {
1637745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
1638745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1639745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
1640745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes}
1642