TabLayout.java revision c7f2aad754af4373c32fcab613de0ba164ada2a2
1320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/*
2320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Copyright (C) 2015 The Android Open Source Project
3320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
4320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * you may not use this file except in compliance with the License.
6320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You may obtain a copy of the License at
7320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
8320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
10320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Unless required by applicable law or agreed to in writing, software
11320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * See the License for the specific language governing permissions and
14320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * limitations under the License.
15320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */
16320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
17320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespackage android.support.design.widget;
18320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
19320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.annotation.TargetApi;
20320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.Context;
21320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.ColorStateList;
22c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.content.res.Resources;
23320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.TypedArray;
2438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.database.DataSetObserver;
25320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Canvas;
26320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Paint;
27320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.drawable.Drawable;
28320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.os.Build;
2984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banesimport android.support.annotation.ColorInt;
3040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.DrawableRes;
31320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.annotation.IntDef;
3240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.LayoutRes;
3340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.NonNull;
3440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.Nullable;
3540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.StringRes;
36320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.design.R;
3738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.support.v4.util.Pools;
38320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.GravityCompat;
39320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.PagerAdapter;
40320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewCompat;
41320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewPager;
42092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banesimport android.support.v4.widget.TextViewCompat;
43320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v7.app.ActionBar;
4466698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport android.support.v7.widget.AppCompatDrawableManager;
45c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.text.Layout;
46320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.text.TextUtils;
47320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.util.AttributeSet;
48c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.util.TypedValue;
49320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.Gravity;
50320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.LayoutInflater;
51320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.View;
52320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewGroup;
53320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewParent;
54320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityEvent;
55320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityNodeInfo;
56320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.HorizontalScrollView;
57320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.ImageView;
58320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.LinearLayout;
59320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.TextView;
60320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.Toast;
61320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
62320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.Retention;
63320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.RetentionPolicy;
64745636602593f0b8ae508eb10266a3c7c675e746Chris Banesimport java.lang.ref.WeakReference;
65320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.ArrayList;
66320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.Iterator;
67320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
6838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
6938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
7038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
7138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
72320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/**
739fb154338a62edc2c57dc036895199d6f1769400Chris Banes * TabLayout provides a horizontal layout to display tabs.
749fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
759fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Population of the tabs to display is
76320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can
77320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)}
789fb154338a62edc2c57dc036895199d6f1769400Chris Banes * respectively. To display the tab, you need to add it to the layout via one of the
799fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #addTab(Tab)} methods. For example:
80320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <pre>
81320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * TabLayout tabLayout = ...;
82320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
83320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
84320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
85320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </pre>
86320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
87320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * notified when any tab's selection state has been changed.
88320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <p>
89320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * If you're using a {@link android.support.v4.view.ViewPager} together
9038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * with this layout, you can use {@link #setupWithViewPager(ViewPager)} to link the two together.
9138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p>
92320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes *
93320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
94320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */
95320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespublic class TabLayout extends HorizontalScrollView {
96320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
97c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps
98c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private static final int DEFAULT_GAP_TEXT_ICON = 8; // dps
99cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private static final int INVALID_WIDTH = -1;
100320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int DEFAULT_HEIGHT = 48; // dps
101320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
102320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
103320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int MOTION_NON_ADJACENT_OFFSET = 24;
104320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
105320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private static final int ANIMATION_DURATION = 300;
106320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
10738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16);
10838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
109320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
110320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab
111320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * labels and a larger number of tabs. They are best used for browsing contexts in touch
112320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * interfaces when users don’t need to directly compare the tab labels.
113320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
114320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
115320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
116320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
117320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_SCROLLABLE = 0;
118320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
119320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
120320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs display all tabs concurrently and are best used with content that benefits from
121320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * quick pivots between tabs. The maximum number of tabs is limited by the view’s width.
122320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Fixed tabs have equal width, based on the widest tab label.
123320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
124320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
125320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabMode()
126320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
127320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int MODE_FIXED = 1;
128320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
129320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
130320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
131320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
132320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED})
133320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
134320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface Mode {}
135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
136320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
137320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect
138320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * when used with {@link #MODE_FIXED}.
139320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
140320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
141320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
142320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
143320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_FILL = 0;
144320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
145320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
146320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Gravity used to lay out the tabs in the center of the {@link TabLayout}.
147320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
148320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabGravity(int)
149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #getTabGravity()
150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
151320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final int GRAVITY_CENTER = 1;
152320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
153320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @hide
155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
156320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER})
157320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Retention(RetentionPolicy.SOURCE)
158320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public @interface TabGravity {}
159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
161320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Callback interface invoked when a tab's selection state changes.
162320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public interface OnTabSelectedListener {
164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab enters the selected state.
167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was selected
169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabSelected(Tab tab);
171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab exits the selected state.
174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was unselected
176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabUnselected(Tab tab);
178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Called when a tab that is already selected is chosen again by the user. Some applications
181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * may use this action to return to the top level of a category.
182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tab The tab that was reselected.
184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onTabReselected(Tab tab);
186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final ArrayList<Tab> mTabs = new ArrayList<>();
189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private Tab mSelectedTab;
190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final SlidingTabStrip mTabStrip;
192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingStart;
194320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingTop;
195320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingEnd;
196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabPaddingBottom;
197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
19845cbb1d10c865209f373a148556191700d8ea345Chris Banes    private int mTabTextAppearance;
19945cbb1d10c865209f373a148556191700d8ea345Chris Banes    private ColorStateList mTabTextColors;
200c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private float mTabTextSize;
201c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private float mTabTextMultiLineSize;
20245cbb1d10c865209f373a148556191700d8ea345Chris Banes
203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mTabBackgroundResId;
204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
205d67a60744b4a713ca37e219e349b55743a39272aChris Banes    private int mTabMaxWidth = Integer.MAX_VALUE;
206cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private final int mRequestedTabMinWidth;
207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private final int mRequestedTabMaxWidth;
208cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private final int mScrollableTabMinWidth;
209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mContentInsetStart;
211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mTabGravity;
213320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int mMode;
214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private OnTabSelectedListener mOnTabSelectedListener;
216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
217bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes    private ValueAnimatorCompat mScrollAnimator;
218bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
21938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private ViewPager mViewPager;
22038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private PagerAdapter mPagerAdapter;
22138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private DataSetObserver mPagerAdapterObserver;
22238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private TabLayoutOnPageChangeListener mPageChangeListener;
22338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
22438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    // Pool we use as a simple RecyclerBin
22538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12);
22638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
227320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context) {
228320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        this(context, null);
229320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs) {
232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        this(context, attrs, 0);
233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super(context, attrs, defStyleAttr);
237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
238809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes        ThemeUtils.checkAppCompatTheme(context);
239809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes
240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Disable the Scroll Bar
241320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        setHorizontalScrollBarEnabled(false);
242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Add the TabStrip
244320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip = new SlidingTabStrip(context);
245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                defStyleAttr, R.style.Widget_Design_TabLayout);
249320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
250320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorHeight(
251320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
252320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
254320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
255320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
256320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
257320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingStart);
258320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
259320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingTop);
260320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
261320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingEnd);
262320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabPaddingBottom);
264320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
265c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
266c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                R.style.TextAppearance_Design_Tab);
267c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
268c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        // Text colors/sizes come from the text appearance first
269c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
270c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                R.styleable.TextAppearance);
271c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        try {
272c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            mTabTextSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
273c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            mTabTextColors = ta.getColorStateList(R.styleable.TextAppearance_android_textColor);
274c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        } finally {
275c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            ta.recycle();
276c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        }
27745cbb1d10c865209f373a148556191700d8ea345Chris Banes
27845cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (a.hasValue(R.styleable.TabLayout_tabTextColor)) {
27945cbb1d10c865209f373a148556191700d8ea345Chris Banes            // If we have an explicit text color set, use it instead
28045cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor);
28145cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
28245cbb1d10c865209f373a148556191700d8ea345Chris Banes
283320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
28445cbb1d10c865209f373a148556191700d8ea345Chris Banes            // We have an explicit selected text color set, so we need to make merge it with the
28545cbb1d10c865209f373a148556191700d8ea345Chris Banes            // current colors. This is exposed so that developers can use theme attributes to set
28645cbb1d10c865209f373a148556191700d8ea345Chris Banes            // this (theme attrs in ColorStateLists are Lollipop+)
28745cbb1d10c865209f373a148556191700d8ea345Chris Banes            final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
28845cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected);
289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
291cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,
292cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                INVALID_WIDTH);
293cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,
294cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                INVALID_WIDTH);
295320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
296320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
297320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
298320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
299320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        a.recycle();
300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
301cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        // TODO add attr for these
302cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        final Resources res = getResources();
303cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
304cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
305c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
306320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now apply the tab mode and gravity
307320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        applyModeAndGravity();
308320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
309320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
31184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's color for the currently selected tab.
31284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
31384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param color color to use for the indicator
31484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
31584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorColor(@ColorInt int color) {
31684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorColor(color);
31784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
31884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
31984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
32084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * Sets the tab indicator's height for the currently selected tab.
32184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     *
32284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     * @param height height to use for the indicator in pixels
32384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes     */
32484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    public void setSelectedTabIndicatorHeight(int height) {
32584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes        mTabStrip.setSelectedIndicatorHeight(height);
32684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    }
32784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes
32884a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes    /**
329320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
330745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
331320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <p>
332320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Calling this method does not update the selected tab, it is only used for drawing purposes.
333ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     *
334ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param position current scroll position
335ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
336ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes     * @param updateSelectedText Whether to update the text's selected state.
337320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
338ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes    public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
3393fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        setScrollPosition(position, positionOffset, updateSelectedText, true);
3403fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes    }
3413fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
3423fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes    private void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
3433fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            boolean updateIndicatorPosition) {
3443fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        final int roundedPosition = Math.round(position + positionOffset);
3453fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
347320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
3483fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
3493fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Set the indicator position, if enabled
3503fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (updateIndicatorPosition) {
3513fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
3543fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Now update the scroll position, canceling any running animation
3553fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
3563fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            mScrollAnimator.cancel();
3573fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        }
358320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        scrollTo(calculateScrollXForTab(position, positionOffset), 0);
359320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
3603fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        // Update the 'selected state' view as we scroll, if enabled
361ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        if (updateSelectedText) {
3623fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            setSelectedTabView(roundedPosition);
363ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes        }
364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
365320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
36609db0f00a693704eb747027def4279362c51894eChris Banes    private float getScrollPosition() {
36709db0f00a693704eb747027def4279362c51894eChris Banes        return mTabStrip.getIndicatorPosition();
36809db0f00a693704eb747027def4279362c51894eChris Banes    }
36909db0f00a693704eb747027def4279362c51894eChris Banes
370320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
371320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
373320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
374320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
375320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
37640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab) {
377320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, mTabs.isEmpty());
378320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
379320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
380320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
382320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * If this is the first tab to be added it will become the selected tab.
383320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
384320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
385320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
386320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
38740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position) {
388320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTab(tab, position, mTabs.isEmpty());
389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
390320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be added at the end of the list.
393320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab Tab to add
395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
39740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, boolean setSelected) {
398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
399320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
400320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
401320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
402320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, setSelected);
403320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, mTabs.size());
404320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
405320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
406320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
408320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
411320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
412320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to add
413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position The new position of the tab
414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param setSelected True if the added tab should become the selected tab.
415320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
41640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
417320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
418320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
421320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        addTabView(tab, position, setSelected);
422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        configureTab(tab, position);
423320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
424320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tab.select();
425320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
426320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
427320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
428320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
429745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will
430745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * handle switching to and from tabs.
431320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
432320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param onTabSelectedListener Listener to handle tab selection events
433320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
434320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
435320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mOnTabSelectedListener = onTabSelectedListener;
436320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
437320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
438320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
439320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Create and return a new {@link Tab}. You need to manually add this using
440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * {@link #addTab(Tab)} or a related method.
441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return A new Tab
443320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #addTab(Tab)
444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
44540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @NonNull
446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab newTab() {
4473a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        Tab tab = sTabPool.acquire();
4483a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        if (tab == null) {
4493a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            tab = new Tab(this);
4503a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
4513a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        tab.mView = createTabView(tab);
4523a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        return tab;
453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the number of tabs currently registered with the action bar.
457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return Tab count
459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabCount() {
461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.size();
462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the tab at the specified index.
466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
46740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public Tab getTabAt(int index) {
469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabs.get(index);
470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
4731a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * Returns the position of the current selected tab.
4741a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     *
4751a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     * @return selected tab position, or {@code -1} if there isn't a selected tab.
4761a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes     */
4771a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    public int getSelectedTabPosition() {
4781a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
4791a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    }
4801a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
4811a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes    /**
482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
484320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
485320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param tab The tab to remove
486320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
487320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTab(Tab tab) {
488320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (tab.mParent != this) {
489320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            throw new IllegalArgumentException("Tab does not belong to this TabLayout.");
490320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
491320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
492320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabAt(tab.getPosition());
493320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
494320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
495320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
496320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove a tab from the layout. If the removed tab was selected it will be deselected
497320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and another tab will be selected if present.
498320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
499320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param position Position of the tab to remove
500320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
501320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeTabAt(int position) {
502320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
503320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        removeTabViewAt(position);
504320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
50538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        final Tab removedTab = mTabs.remove(position);
506320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (removedTab != null) {
50738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            removedTab.reset();
50838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            sTabPool.release(removedTab);
509320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
510320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int newTabCount = mTabs.size();
512320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position; i < newTabCount; i++) {
513320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
514320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
515320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
516320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (selectedTabPosition == position) {
517320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
518320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
519320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
520320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
521320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
522320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Remove all tabs from the action bar and deselect the current tab.
523320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
524320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void removeAllTabs() {
525320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Remove all the views
52638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) {
52738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            removeTabViewAt(i);
52838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
53038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) {
53138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final Tab tab = i.next();
532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            i.remove();
53338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            tab.reset();
53438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            sTabPool.release(tab);
535320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
536bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
537bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mSelectedTab = null;
538320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
539320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
540320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
541320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the behavior mode for the Tabs in this layout. The valid input options are:
542320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <ul>
543320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used
544320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * with content that benefits from quick pivots between tabs.</li>
545320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
546320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * and can contain longer tab labels and a larger number of tabs. They are best used for
547320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * browsing contexts in touch interfaces when users don’t need to directly compare the tab
548745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li>
549320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * </ul>
550320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
551320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
552320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
553320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabMode(@Mode int mode) {
554320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mode != mMode) {
555320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mMode = mode;
556320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
557320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
558320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
559320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
560320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
561320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Returns the current mode used by this {@link TabLayout}.
562320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
563320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @see #setTabMode(int)
564320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
565320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Mode
566320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabMode() {
567320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mMode;
568320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
569320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
570320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
571320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * Set the gravity to use when laying out the tabs.
572320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
573320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
574320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
575320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public void setTabGravity(@TabGravity int gravity) {
576320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mTabGravity != gravity) {
577320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabGravity = gravity;
578320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            applyModeAndGravity();
579320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
580320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
581320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
582320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
583320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * The current gravity used for laying out tabs.
584320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     *
585320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
586320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
587320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @TabGravity
588320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public int getTabGravity() {
589320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return mTabGravity;
590320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
591320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
592320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
59345cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
594320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
59540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    public void setTabTextColors(@Nullable ColorStateList textColor) {
59645cbb1d10c865209f373a148556191700d8ea345Chris Banes        if (mTabTextColors != textColor) {
59745cbb1d10c865209f373a148556191700d8ea345Chris Banes            mTabTextColors = textColor;
59845cbb1d10c865209f373a148556191700d8ea345Chris Banes            updateAllTabs();
599320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
600320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
601320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
602320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
60345cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Gets the text colors for the different states (normal, selected) used for the tabs.
604320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
60540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes    @Nullable
60645cbb1d10c865209f373a148556191700d8ea345Chris Banes    public ColorStateList getTabTextColors() {
60745cbb1d10c865209f373a148556191700d8ea345Chris Banes        return mTabTextColors;
60845cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
60945cbb1d10c865209f373a148556191700d8ea345Chris Banes
61045cbb1d10c865209f373a148556191700d8ea345Chris Banes    /**
61145cbb1d10c865209f373a148556191700d8ea345Chris Banes     * Sets the text colors for the different states (normal, selected) used for the tabs.
61245cbb1d10c865209f373a148556191700d8ea345Chris Banes     */
61345cbb1d10c865209f373a148556191700d8ea345Chris Banes    public void setTabTextColors(int normalColor, int selectedColor) {
61445cbb1d10c865209f373a148556191700d8ea345Chris Banes        setTabTextColors(createColorStateList(normalColor, selectedColor));
61545cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
61645cbb1d10c865209f373a148556191700d8ea345Chris Banes
617745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
618745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
619745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
62038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * <p>This method will link the given ViewPager and this TabLayout together so that any
62138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * changes in one are automatically reflected in the other. This includes adapter changes,
62238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * scroll state changes, and clicks. The tabs displayed in this layout will be populated
62338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * from the ViewPager adapter's page titles.</p>
62438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *
62538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * <p>After this method is called, you will not need this method again unless you want
62638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * to change the linked ViewPager.</p>
627745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
62838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * <p>If the given ViewPager is non-null, it needs to already have a
62938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * {@link PagerAdapter} set.</p>
63038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *
63138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * @param viewPager The ViewPager to link, or {@code null} to clear any previous link.
632745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
63338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    public void setupWithViewPager(@Nullable final ViewPager viewPager) {
63438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (mViewPager != null && mPageChangeListener != null) {
63538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // If we've already been setup with a ViewPager, remove us from it
63638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mViewPager.removeOnPageChangeListener(mPageChangeListener);
637745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
638745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
63938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (viewPager != null) {
64038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final PagerAdapter adapter = viewPager.getAdapter();
64138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (adapter == null) {
64238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
64338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
644745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
64538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mViewPager = viewPager;
646112f648090b48c0d00a51983baf148dbbc800abbChris Banes
64738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Add our custom OnPageChangeListener to the ViewPager
64838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mPageChangeListener == null) {
64938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mPageChangeListener = new TabLayoutOnPageChangeListener(this);
65009db0f00a693704eb747027def4279362c51894eChris Banes            }
65138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPageChangeListener.reset();
65238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            viewPager.addOnPageChangeListener(mPageChangeListener);
65338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
65438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Now we'll add a tab selected listener to set ViewPager's current item
65538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
65638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
65738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Now we'll populate ourselves from the pager adapter
65838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setPagerAdapter(adapter, true);
65938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        } else {
66038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // We've been given a null ViewPager so we need to clear out the internal state,
66138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // listeners and observers
66238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mViewPager = null;
66338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setOnTabSelectedListener(null);
66438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setPagerAdapter(null, true);
665112f648090b48c0d00a51983baf148dbbc800abbChris Banes        }
666745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
667745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
668745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
66938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a TabLayout with a ViewPager
67038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *             together. When that method is used, the TabLayout will be automatically updated
67138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes     *             when the {@link PagerAdapter} is changed.
672745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
67338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    @Deprecated
67438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    public void setTabsFromPagerAdapter(@Nullable final PagerAdapter adapter) {
67538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        setPagerAdapter(adapter, false);
67638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
67738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
67838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
67938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (mPagerAdapter != null && mPagerAdapterObserver != null) {
68038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // If we already have a PagerAdapter, unregister our observer
68138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
68238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
68338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
68438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        mPagerAdapter = adapter;
68538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
68638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (addObserver && adapter != null) {
68738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Register our observer on the new adapter
68838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mPagerAdapterObserver == null) {
68938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mPagerAdapterObserver = new PagerAdapterObserver();
69038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
69138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            adapter.registerDataSetObserver(mPagerAdapterObserver);
69238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
69338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
69438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        // Finally make sure we reflect the new adapter
69538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        populateFromPagerAdapter();
69638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
69738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
69838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private void populateFromPagerAdapter() {
699745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        removeAllTabs();
70038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
70138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (mPagerAdapter != null) {
70238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final int adapterCount = mPagerAdapter.getCount();
70338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            for (int i = 0; i < adapterCount; i++) {
70438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
70538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
70638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
70738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            // Make sure we reflect the currently set ViewPager item
70838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (mViewPager != null && adapterCount > 0) {
70938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final int curItem = mViewPager.getCurrentItem();
71038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
71138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                    selectTab(getTabAt(curItem));
71238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                }
71338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
71438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        } else {
71538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            removeAllTabs();
716745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
717745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
718745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
71945cbb1d10c865209f373a148556191700d8ea345Chris Banes    private void updateAllTabs() {
7203a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        for (int i = 0, z = mTabs.size(); i < z; i++) {
7213a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            mTabs.get(i).updateView();
72245cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
723320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
724320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
72538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private TabView createTabView(@NonNull final Tab tab) {
72638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
72738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (tabView == null) {
72838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            tabView = new TabView(getContext());
72938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
73038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        tabView.setTab(tab);
731320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tabView.setFocusable(true);
732cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        tabView.setMinimumWidth(getTabMinWidth());
733320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return tabView;
734320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
735320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
736320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void configureTab(Tab tab, int position) {
737320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        tab.setPosition(position);
738320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabs.add(position, tab);
739320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
740320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int count = mTabs.size();
741320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = position + 1; i < count; i++) {
742320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTabs.get(i).setPosition(i);
743320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
744320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
745320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
746320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, boolean setSelected) {
7473a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        final TabView tabView = tab.mView;
748320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, createLayoutParamsForTabs());
749320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
750320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
751320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
752320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
753320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
754320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void addTabView(Tab tab, int position, boolean setSelected) {
7553a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        final TabView tabView = tab.mView;
756320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
757320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (setSelected) {
758320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            tabView.setSelected(true);
759320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
760320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
761320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
762320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
763320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
764320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
765320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        updateTabViewLayoutParams(lp);
766320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return lp;
767320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
768320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
769320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
770320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
771320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = 0;
772320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 1;
773320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
774320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
775320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            lp.weight = 0;
776320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
777320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
778320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
779320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int dpToPx(int dps) {
780320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return Math.round(getResources().getDisplayMetrics().density * dps);
781320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
782320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
783320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    @Override
784320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
785320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // If we have a MeasureSpec which allows us to decide our height, try and use the default
786320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // height
787c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
788320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (MeasureSpec.getMode(heightMeasureSpec)) {
789320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.AT_MOST:
790320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
791bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
792320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        MeasureSpec.EXACTLY);
793320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
794320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MeasureSpec.UNSPECIFIED:
795bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
796320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
797320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
798320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
799cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
800cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
801cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // If we don't have an unspecified width spec, use the given size to calculate
802cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // the max tab width
803cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            mTabMaxWidth = mRequestedTabMaxWidth > 0
804cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    ? mRequestedTabMaxWidth
805cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
806cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        }
807cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
808320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now super measure itself using the (possibly) modified height spec
809320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
810320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
811cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (getChildCount() == 1) {
812320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're in fixed mode then we need to make the tab strip is the same width as us
813320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // so we don't scroll
814320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View child = getChildAt(0);
815cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            boolean remeasure = false;
816cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
817cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            switch (mMode) {
818cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                case MODE_SCROLLABLE:
819cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // We only need to resize the child if it's smaller than us. This is similar
820cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // to fillViewport
821cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = child.getMeasuredWidth() < getMeasuredWidth();
822cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    break;
823cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                case MODE_FIXED:
824cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // Resize the child so that it doesn't scroll
825cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = child.getMeasuredWidth() != getMeasuredWidth();
826cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    break;
827cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            }
828320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
829cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            if (remeasure) {
830cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                // Re-measure the child with a widthSpec set to be exactly our measure width
831320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
832320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        + getPaddingBottom(), child.getLayoutParams().height);
833cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
834cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        getMeasuredWidth(), MeasureSpec.EXACTLY);
835320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
836320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
837320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
838320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
839320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
840320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void removeTabViewAt(int position) {
84138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        final TabView view = (TabView) mTabStrip.getChildAt(position);
842320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        mTabStrip.removeViewAt(position);
84338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        if (view != null) {
84438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            view.reset();
84538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mTabViewPool.release(view);
84638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
847320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        requestLayout();
848320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
849320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
850320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void animateToTab(int newPosition) {
851320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (newPosition == Tab.INVALID_POSITION) {
852320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
853320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
8547c71281dc4879e7889c65283aabaeed68521fb14Alan Jeon
855bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
856bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                || mTabStrip.childrenNeedLayout()) {
857320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we don't have a window token, or we haven't been laid out yet just draw the new
858320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // position now
859ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes            setScrollPosition(newPosition, 0f, true);
860320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return;
861320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
862320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
863320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int startScrollX = getScrollX();
864320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int targetScrollX = calculateScrollXForTab(newPosition, 0);
865320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
866320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (startScrollX != targetScrollX) {
867bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            if (mScrollAnimator == null) {
868bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator = ViewUtils.createAnimator();
869bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
870bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setDuration(ANIMATION_DURATION);
871bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                mScrollAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
872bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    @Override
873bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
874bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                        scrollTo(animator.getAnimatedIntValue(), 0);
875bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    }
876bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                });
877bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
878bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
879bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.setIntValues(startScrollX, targetScrollX);
880bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            mScrollAnimator.start();
881320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
882320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
883320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        // Now animate the indicator
884bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
885320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
886320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
887320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void setSelectedTabView(int position) {
888320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final int tabCount = mTabStrip.getChildCount();
88909db0f00a693704eb747027def4279362c51894eChris Banes        if (position < tabCount && !mTabStrip.getChildAt(position).isSelected()) {
89009db0f00a693704eb747027def4279362c51894eChris Banes            for (int i = 0; i < tabCount; i++) {
89109db0f00a693704eb747027def4279362c51894eChris Banes                final View child = mTabStrip.getChildAt(i);
89209db0f00a693704eb747027def4279362c51894eChris Banes                child.setSelected(i == position);
89309db0f00a693704eb747027def4279362c51894eChris Banes            }
894320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
895320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
896320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
897320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    void selectTab(Tab tab) {
89815ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes        selectTab(tab, true);
89915ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes    }
90015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes
90115ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes    void selectTab(Tab tab, boolean updateIndicator) {
902320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mSelectedTab == tab) {
903320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null) {
904320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mOnTabSelectedListener != null) {
905320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mOnTabSelectedListener.onTabReselected(mSelectedTab);
906320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
907320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                animateToTab(tab.getPosition());
908320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
909320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        } else {
91015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes            if (updateIndicator) {
911854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
912854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                if (newPosition != Tab.INVALID_POSITION) {
913854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                    setSelectedTabView(newPosition);
914854d6b89f46fb2877c009eaa80394b8c8079f7eeChris Banes                }
91515ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
91615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                        && newPosition != Tab.INVALID_POSITION) {
91715ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    // If we don't currently have a tab, just draw the indicator
91815ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    setScrollPosition(newPosition, 0f, true);
91915ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                } else {
92015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                    animateToTab(newPosition);
92115ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes                }
922320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
923320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null && mOnTabSelectedListener != null) {
924320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mOnTabSelectedListener.onTabUnselected(mSelectedTab);
925320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
926320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedTab = tab;
927320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mSelectedTab != null && mOnTabSelectedListener != null) {
928320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mOnTabSelectedListener.onTabSelected(mSelectedTab);
929320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
930320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
931320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
932320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
933320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private int calculateScrollXForTab(int position, float positionOffset) {
934320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
935320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedChild = mTabStrip.getChildAt(position);
936320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View nextChild = position + 1 < mTabStrip.getChildCount()
937320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    ? mTabStrip.getChildAt(position + 1)
938320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    : null;
939320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
940320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
942bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return selectedChild.getLeft()
943bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f))
944bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    + (selectedChild.getWidth() / 2)
945bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    - (getWidth() / 2);
946320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
947320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        return 0;
948320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
949320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
950320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private void applyModeAndGravity() {
951320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        int paddingStart = 0;
952320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        if (mMode == MODE_SCROLLABLE) {
953320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // If we're scrollable, or fixed at start, inset using padding
954320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
955320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
956320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
957320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
958320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        switch (mMode) {
959320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_FIXED:
960320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
961320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
962320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            case MODE_SCROLLABLE:
963320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mTabStrip.setGravity(GravityCompat.START);
964320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                break;
965320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
966320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
967cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        updateTabViews(true);
968320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
969320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
970cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private void updateTabViews(final boolean requestLayout) {
971320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        for (int i = 0; i < mTabStrip.getChildCount(); i++) {
972320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            View child = mTabStrip.getChildAt(i);
973cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            child.setMinimumWidth(getTabMinWidth());
974320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
975cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            if (requestLayout) {
976cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                child.requestLayout();
977cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            }
978320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
979320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
980320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
981320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    /**
982320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     * A tab in this layout. Instances can be created via {@link #newTab()}.
983320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes     */
984320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    public static final class Tab {
985320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
986320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
987320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * An invalid position for a tab.
988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getPosition()
990320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public static final int INVALID_POSITION = -1;
992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Object mTag;
994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private Drawable mIcon;
995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mText;
996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private CharSequence mContentDesc;
997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mPosition = INVALID_POSITION;
998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1000320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final TabLayout mParent;
10013a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        private TabView mView;
1002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        Tab(TabLayout parent) {
1004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mParent = parent;
1005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1006320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1007320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1008320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return This Tab's tag object.
1009320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
101040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1011320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Object getTag() {
1012320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTag;
1013320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1014320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1015320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1016320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Give this Tab an arbitrary object to hold for later use.
1017320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1018320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param tag Object to store
1019320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1020320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
102140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
102240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setTag(@Nullable Object tag) {
1023320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mTag = tag;
1024320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1025320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1026320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
102721f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes
102821f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        /**
102921f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * Returns the custom view used for this tab.
103021f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         *
103121f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(View)
103221f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         * @see #setCustomView(int)
103321f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes         */
103421f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        @Nullable
103521f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes        public View getCustomView() {
1036320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mCustomView;
1037320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1038320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1039320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
104040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
104140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
104240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the provided view contains a {@link TextView} with an ID of
104340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
104440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
104540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
104640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
104740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
1048320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1049320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param view Custom view to be used as a tab.
1050320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1051320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
105240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
105340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setCustomView(@Nullable View view) {
1054320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mCustomView = view;
10553a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1056320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1057320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1058320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1059320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
106040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * Set a custom view to be used for this tab.
106140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * <p>
106240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * If the inflated layout contains a {@link TextView} with an ID of
106340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link android.R.id#text1} then that will be updated with the value given
106440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * to {@link #setText(CharSequence)}. Similarly, if this layout contains an
106540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with
106640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * the value given to {@link #setIcon(Drawable)}.
106740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes         * </p>
1068320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1069b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes         * @param resId A layout resource to inflate and use as a custom tab view
1070320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1071320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
107240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
1073b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes        public Tab setCustomView(@LayoutRes int resId) {
10743a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            final LayoutInflater inflater = LayoutInflater.from(mView.getContext());
10753a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            return setCustomView(inflater.inflate(resId, mView, false));
1076320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1077320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1078320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1079320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the icon associated with this tab.
1080320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1081320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's icon
1082320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
108340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1084320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Drawable getIcon() {
1085320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mIcon;
1086320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1087320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1088320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1089320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the current position of this tab in the action bar.
1090320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1091320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
1092320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the action bar.
1093320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1094320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public int getPosition() {
1095320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mPosition;
1096320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1097320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1098320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setPosition(int position) {
1099320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mPosition = position;
1100320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1101320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1102320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1103320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Return the text of this tab.
1104320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1105320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The tab's text
1106320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
110740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1108320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getText() {
1109320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mText;
1110320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1111320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1112320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1113320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1114320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1115320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param icon The drawable to use as an icon
1116320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1117320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
111840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
111940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@Nullable Drawable icon) {
1120320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mIcon = icon;
11213a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1122320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1123320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1124320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1125320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1126320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the icon displayed on this tab.
1127320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1128320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the icon that should be displayed
1129320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1130320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
113140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
113240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setIcon(@DrawableRes int resId) {
11337e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes            return setIcon(AppCompatDrawableManager.get().getDrawable(mParent.getContext(), resId));
1134320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1136320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1137320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1138320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1139320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1140320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param text The text to display
1141320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1142320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
114340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
114440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@Nullable CharSequence text) {
1145320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mText = text;
11463a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1147320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1148320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1151320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set the text displayed on this tab. Text may be truncated if there is not room to display
1152320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * the entire string.
1153320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the text that should be displayed
1155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1156320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
115740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
115840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setText(@StringRes int resId) {
1159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setText(mParent.getResources().getText(resId));
1160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1161320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1162320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Select this tab. Only valid if the tab has been added to the action bar.
1164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
1165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void select() {
1166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mParent.selectTab(this);
1167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
11701a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         * Returns true if this tab is currently selected.
11711a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes         */
11721a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        public boolean isSelected() {
11731a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes            return mParent.getSelectedTabPosition() == mPosition;
11741a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        }
11751a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes
11761a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes        /**
1177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param resId A resource ID referring to the description text
1181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
118540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
118640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@StringRes int resId) {
1187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return setContentDescription(mParent.getResources().getText(resId));
1188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Set a description of this tab's content for use in accessibility support. If no content
1192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * description is provided the title will be used.
1193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1194320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @param contentDesc Description of this tab's content
1195320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return The current instance for call chaining
1196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #getContentDescription()
1198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
119940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @NonNull
120040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        public Tab setContentDescription(@Nullable CharSequence contentDesc) {
1201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mContentDesc = contentDesc;
12023a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            updateView();
1203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return this;
1204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        /**
1207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * Gets a brief description of this tab's content for use in accessibility support.
1208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         *
1209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @return Description of this tab's content
1210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(CharSequence)
1211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         * @see #setContentDescription(int)
1212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes         */
121340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        @Nullable
1214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public CharSequence getContentDescription() {
1215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mContentDesc;
1216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
121738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
12183a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        private void updateView() {
12193a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            if (mView != null) {
12203a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                mView.update();
12213a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            }
12223a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
12233a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
122438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
12253a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            mView = null;
122638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mTag = null;
122738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mIcon = null;
122838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mText = null;
122938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mContentDesc = null;
123038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPosition = INVALID_POSITION;
123138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mCustomView = null;
123238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
1233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    class TabView extends LinearLayout implements OnLongClickListener {
123638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private Tab mTab;
1237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private TextView mTextView;
1238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private ImageView mIconView;
123940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
1240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private View mCustomView;
124140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private TextView mCustomTextView;
124240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        private ImageView mCustomIconView;
1243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1244c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        private int mDefaultMaxLines = 2;
1245c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
124638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public TabView(Context context) {
1247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mTabBackgroundResId != 0) {
12497e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes                setBackgroundDrawable(
12507e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes                        AppCompatDrawableManager.get().getDrawable(context, mTabBackgroundResId));
1251320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1252320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
1253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabPaddingEnd, mTabPaddingBottom);
1254320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setGravity(Gravity.CENTER);
1255c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            setOrientation(VERTICAL);
12563a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            setClickable(true);
12573a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        }
12583a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
12593a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        @Override
12603a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes        public boolean performClick() {
12613a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            final boolean value = super.performClick();
12623a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes
12633a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            if (mTab != null) {
12643a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                mTab.select();
12653a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                return true;
12663a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            } else {
12673a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes                return value;
12683a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes            }
1269320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1270320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1271320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1272320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void setSelected(boolean selected) {
1273320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean changed = (isSelected() != selected);
1274320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.setSelected(selected);
1275320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (changed && selected) {
1276320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1277320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1278320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1279320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setSelected(selected);
1280320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1281320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1282320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setSelected(selected);
1283320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1284320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1285320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1286320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1287320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1288320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityEvent(event);
1291320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1292320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            event.setClassName(ActionBar.Tab.class.getName());
1293320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1294320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1295320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1296320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1297320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1298320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onInitializeAccessibilityNodeInfo(info);
1299320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // This view masquerades as an action bar tab.
1300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            info.setClassName(ActionBar.Tab.class.getName());
1301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
13044172f25e47c484612b50143da44878003db238d1Chris Banes        public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) {
13054172f25e47c484612b50143da44878003db238d1Chris Banes            final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec);
13064172f25e47c484612b50143da44878003db238d1Chris Banes            final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec);
1307cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            final int maxWidth = getTabMaxWidth();
13084172f25e47c484612b50143da44878003db238d1Chris Banes
13094172f25e47c484612b50143da44878003db238d1Chris Banes            final int widthMeasureSpec;
13104172f25e47c484612b50143da44878003db238d1Chris Banes            final int heightMeasureSpec = origHeightMeasureSpec;
13114172f25e47c484612b50143da44878003db238d1Chris Banes
13124172f25e47c484612b50143da44878003db238d1Chris Banes            if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED
13134172f25e47c484612b50143da44878003db238d1Chris Banes                    || specWidthSize > maxWidth)) {
13144172f25e47c484612b50143da44878003db238d1Chris Banes                // If we have a max width and a given spec which is either unspecified or
13154172f25e47c484612b50143da44878003db238d1Chris Banes                // larger than the max width, update the width spec using the same mode
1316c7f2aad754af4373c32fcab613de0ba164ada2a2Kirill Grouchnikov                widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST);
1317cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            } else {
13184172f25e47c484612b50143da44878003db238d1Chris Banes                // Else, use the original width spec
13194172f25e47c484612b50143da44878003db238d1Chris Banes                widthMeasureSpec = origWidthMeasureSpec;
1320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1321c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
13224172f25e47c484612b50143da44878003db238d1Chris Banes            // Now lets measure
13234172f25e47c484612b50143da44878003db238d1Chris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
13244172f25e47c484612b50143da44878003db238d1Chris Banes
1325c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            // We need to switch the text size based on whether the text is spanning 2 lines or not
1326c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (mTextView != null) {
1327c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final Resources res = getResources();
1328c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                float textSize = mTabTextSize;
1329c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                int maxLines = mDefaultMaxLines;
1330c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1331c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
1332c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // If the icon view is being displayed, we limit the text to 1 line
1333c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    maxLines = 1;
1334c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                } else if (mTextView != null && mTextView.getLineCount() > 1) {
1335c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // Otherwise when we have text which wraps we reduce the text size
1336c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    textSize = mTabTextMultiLineSize;
1337c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1338c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1339c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final float curTextSize = mTextView.getTextSize();
1340c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                final int curLineCount = mTextView.getLineCount();
1341092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                final int curMaxLines = TextViewCompat.getMaxLines(mTextView);
1342c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1343092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) {
1344c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // We've got a new text size and/or max lines...
1345c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    boolean updateTextView = true;
1346c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1347c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) {
1348c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // If we're in fixed mode, going up in text size and currently have 1 line
1349c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // then it's very easy to get into an infinite recursion.
1350c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // To combat that we check to see if the change in text size
1351c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        // will cause a line count change. If so, abort the size change.
1352c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        final Layout layout = mTextView.getLayout();
1353c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        if (layout == null
1354c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                                || approximateLineWidth(layout, 0, textSize) > layout.getWidth()) {
1355c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                            updateTextView = false;
1356c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        }
1357c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    }
1358c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1359c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    if (updateTextView) {
1360c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
1361c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        mTextView.setMaxLines(maxLines);
1362c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1363c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    }
1364c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1365c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
1366320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1367320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
136838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void setTab(@Nullable final Tab tab) {
136938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (tab != mTab) {
137038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                mTab = tab;
137138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                update();
137238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            }
137338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
137438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
137538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
137638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setTab(null);
137738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            setSelected(false);
137838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
137938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
1380320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        final void update() {
1381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Tab tab = mTab;
138238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final View custom = tab != null ? tab.getCustomView() : null;
1383320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (custom != null) {
1384320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final ViewParent customParent = custom.getParent();
1385320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (customParent != this) {
1386320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (customParent != null) {
1387320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        ((ViewGroup) customParent).removeView(custom);
1388320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    addView(custom);
1390320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mCustomView = custom;
1392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mTextView != null) {
1393320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTextView.setVisibility(GONE);
1394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mIconView != null) {
1396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setVisibility(GONE);
1397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mIconView.setImageDrawable(null);
1398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
139940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
140040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
1401c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (mCustomTextView != null) {
1402092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                    mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView);
1403c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
140440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);
1405320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
140640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // We do not have a custom view. Remove one if it already exists
1407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mCustomView != null) {
1408320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    removeView(mCustomView);
1409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mCustomView = null;
1410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
141140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomTextView = null;
141240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mCustomIconView = null;
141340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
141540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (mCustomView == null) {
141640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // If there isn't a custom view, we'll us our own in-built layouts
141740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mIconView == null) {
141840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    ImageView iconView = (ImageView) LayoutInflater.from(getContext())
1419a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_icon, this, false);
142040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(iconView, 0);
142140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mIconView = iconView;
1422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
142340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTextView == null) {
142440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    TextView textView = (TextView) LayoutInflater.from(getContext())
1425a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                            .inflate(R.layout.design_layout_tab_text, this, false);
142640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    addView(textView);
142740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView = textView;
1428092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes                    mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView);
142940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
143040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                mTextView.setTextAppearance(getContext(), mTabTextAppearance);
143140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mTabTextColors != null) {
143240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    mTextView.setTextColor(mTabTextColors);
1433320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
143438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                updateTextAndIcon(mTextView, mIconView);
143540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
143640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                // Else, we'll see if there is a TextView or ImageView present and update them
143740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (mCustomTextView != null || mCustomIconView != null) {
143838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                    updateTextAndIcon(mCustomTextView, mCustomIconView);
143940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                }
144040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
144140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes        }
1442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
144338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void updateTextAndIcon(@Nullable final TextView textView,
144438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                @Nullable final ImageView iconView) {
144538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final Drawable icon = mTab != null ? mTab.getIcon() : null;
144638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final CharSequence text = mTab != null ? mTab.getText() : null;
144738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null;
144840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
144940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (iconView != null) {
145040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (icon != null) {
145140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(icon);
145240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(VISIBLE);
145340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
145440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                } else {
145540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setVisibility(GONE);
145640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    iconView.setImageDrawable(null);
1457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
145838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                iconView.setContentDescription(contentDesc);
145940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
146140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            final boolean hasText = !TextUtils.isEmpty(text);
146240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            if (textView != null) {
146340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                if (hasText) {
146440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(text);
146540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(VISIBLE);
146640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    setVisibility(VISIBLE);
1467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
146840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setVisibility(GONE);
146940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                    textView.setText(null);
1470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
147138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                textView.setContentDescription(contentDesc);
1472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
147340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes
1474c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (iconView != null) {
1475c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams());
1476c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                int bottomMargin = 0;
1477c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (hasText && iconView.getVisibility() == VISIBLE) {
1478c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    // If we're showing both text and icon, add some margin bottom to the icon
1479c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON);
1480c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1481c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                if (bottomMargin != lp.bottomMargin) {
1482c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    lp.bottomMargin = bottomMargin;
1483c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                    iconView.requestLayout();
1484c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                }
1485c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
1486c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
148738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            if (!hasText && !TextUtils.isEmpty(contentDesc)) {
148840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(this);
148940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            } else {
149040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setOnLongClickListener(null);
149140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes                setLongClickable(false);
149240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes            }
1493320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1494320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1495320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1496320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public boolean onLongClick(View v) {
1497320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int[] screenPos = new int[2];
1498320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            getLocationOnScreen(screenPos);
1499320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1500320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final Context context = getContext();
1501320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int width = getWidth();
1502320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int height = getHeight();
1503320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
1504320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1505320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
1506320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    Toast.LENGTH_SHORT);
1507320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // Show under the tab
1508320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
1509320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    (screenPos[0] + width / 2) - screenWidth / 2, height);
1510320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            cheatSheet.show();
1512320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return true;
1513320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1514320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1515320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        public Tab getTab() {
1516320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            return mTab;
1517320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1518c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes
1519c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        /**
1520c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes         * Approximates a given lines width with the new provided text size.
1521c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes         */
1522c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        private float approximateLineWidth(Layout layout, int line, float textSize) {
1523c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize());
1524c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        }
1525320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1526320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1527320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    private class SlidingTabStrip extends LinearLayout {
1528320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedIndicatorHeight;
1529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private final Paint mSelectedIndicatorPaint;
1530320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1531320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mSelectedPosition = -1;
1532320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private float mSelectionOffset;
1533320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1534320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorLeft = -1;
1535320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private int mIndicatorRight = -1;
1536320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
15373fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes        private ValueAnimatorCompat mIndicatorAnimator;
1538ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes
1539320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        SlidingTabStrip(Context context) {
1540320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super(context);
1541320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setWillNotDraw(false);
1542320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedIndicatorPaint = new Paint();
1543320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1544320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1545320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorColor(int color) {
154609db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorPaint.getColor() != color) {
154709db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorPaint.setColor(color);
154809db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
154909db0f00a693704eb747027def4279362c51894eChris Banes            }
1550320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1551320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1552320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setSelectedIndicatorHeight(int height) {
155309db0f00a693704eb747027def4279362c51894eChris Banes            if (mSelectedIndicatorHeight != height) {
155409db0f00a693704eb747027def4279362c51894eChris Banes                mSelectedIndicatorHeight = height;
155509db0f00a693704eb747027def4279362c51894eChris Banes                ViewCompat.postInvalidateOnAnimation(this);
155609db0f00a693704eb747027def4279362c51894eChris Banes            }
1557320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1558320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1559bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        boolean childrenNeedLayout() {
1560bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            for (int i = 0, z = getChildCount(); i < z; i++) {
1561bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                final View child = getChildAt(i);
1562bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                if (child.getWidth() <= 0) {
1563bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                    return true;
1564bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                }
1565bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            }
1566bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes            return false;
1567bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes        }
1568bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes
1569320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
15703fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
15713fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
15723fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            }
15733fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
1574320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectedPosition = position;
1575320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            mSelectionOffset = positionOffset;
1576320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            updateIndicatorPosition();
1577320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1578320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
157909db0f00a693704eb747027def4279362c51894eChris Banes        float getIndicatorPosition() {
158009db0f00a693704eb747027def4279362c51894eChris Banes            return mSelectedPosition + mSelectionOffset;
158109db0f00a693704eb747027def4279362c51894eChris Banes        }
158209db0f00a693704eb747027def4279362c51894eChris Banes
1583320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1584320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
1585320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1586320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1587320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
1588320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // HorizontalScrollView will first measure use with UNSPECIFIED, and then with
1589320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // EXACTLY. Ignore the first call since anything we do will be overwritten anyway
1590320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                return;
1591320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1592320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1593320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
1594320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int count = getChildCount();
1595320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1596cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                // First we'll find the widest tab
1597320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                int largestTabWidth = 0;
1598320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                for (int i = 0, z = count; i < z; i++) {
1599cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    View child = getChildAt(i);
1600cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    if (child.getVisibility() == VISIBLE) {
1601cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
1602cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    }
1603320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1604320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1605320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth <= 0) {
1606320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If we don't have a largest child yet, skip until the next measure pass
1607320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    return;
1608320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1609320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1610320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
1611cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                boolean remeasure = false;
1612cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1613320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
1614320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs fit within our width minus gutters, we will set all tabs to have
1615320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // the same width
1616320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    for (int i = 0; i < count; i++) {
1617cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        final LinearLayout.LayoutParams lp =
1618cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                                (LayoutParams) getChildAt(i).getLayoutParams();
1619cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        if (lp.width != largestTabWidth || lp.weight != 0) {
1620cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            lp.width = largestTabWidth;
1621cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            lp.weight = 0;
1622cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                            remeasure = true;
1623cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                        }
1624320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1625320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1626320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // If the tabs will wrap to be larger than the width minus gutters, we need
1627320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // to switch to GRAVITY_FILL
1628320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    mTabGravity = GRAVITY_FILL;
1629cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    updateTabViews(false);
1630cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    remeasure = true;
1631320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1632320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1633cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                if (remeasure) {
1634cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    // Now re-measure after our changes
1635cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1636cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes                }
1637320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1638320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1639320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1640320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1642320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            super.onLayout(changed, l, t, r, b);
1643ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes
16443fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
1645ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // If we're currently running an animation, lets cancel it and start a
1646ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // new animation with the remaining duration
16473fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
16483fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                final long duration = mIndicatorAnimator.getDuration();
1649ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                animateIndicatorToPosition(mSelectedPosition,
16503fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                        Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));
1651ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes            } else {
1652ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                // If we've been layed out, update the indicator position
1653ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes                updateIndicatorPosition();
1654ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes            }
1655320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1656320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1657320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void updateIndicatorPosition() {
1658320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View selectedTitle = getChildAt(mSelectedPosition);
1659320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            int left, right;
1660320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1661320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (selectedTitle != null && selectedTitle.getWidth() > 0) {
1662320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = selectedTitle.getLeft();
1663320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                right = selectedTitle.getRight();
1664320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1665320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
1666320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // Draw the selection partway between the tabs
1667320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    View nextTitle = getChildAt(mSelectedPosition + 1);
1668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    left = (int) (mSelectionOffset * nextTitle.getLeft() +
1669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * left);
1670320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    right = (int) (mSelectionOffset * nextTitle.getRight() +
1671320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                            (1.0f - mSelectionOffset) * right);
1672320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1673320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1674320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                left = right = -1;
1675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1676320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1677320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            setIndicatorPosition(left, right);
1678320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1679320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1680320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        private void setIndicatorPosition(int left, int right) {
1681320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (left != mIndicatorLeft || right != mIndicatorRight) {
1682320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the indicator's left/right has changed, invalidate
1683320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorLeft = left;
1684320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                mIndicatorRight = right;
1685320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                ViewCompat.postInvalidateOnAnimation(this);
1686320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1687320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1688320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1689320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        void animateIndicatorToPosition(final int position, int duration) {
16903fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
16913fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                mIndicatorAnimator.cancel();
16923fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes            }
16933fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes
1694320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final boolean isRtl = ViewCompat.getLayoutDirection(this)
1695320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    == ViewCompat.LAYOUT_DIRECTION_RTL;
1696320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1697320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final View targetView = getChildAt(position);
1698320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetLeft = targetView.getLeft();
1699320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int targetRight = targetView.getRight();
1700320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startLeft;
1701320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            final int startRight;
1702320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1703320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (Math.abs(position - mSelectedPosition) <= 1) {
1704320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // If the views are adjacent, we'll animate from edge-to-edge
1705320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startLeft = mIndicatorLeft;
1706320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                startRight = mIndicatorRight;
1707320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            } else {
1708320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                // Else, we'll just grow from the nearest edge
1709320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
1710320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                if (position < mSelectedPosition) {
1711320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going end-to-start
1712320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1713320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
1714320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
1715320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1716320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1717320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                } else {
1718320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    // We're going start-to-end
1719320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    if (isRtl) {
1720320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetRight + offset;
1721320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    } else {
1722320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        startLeft = startRight = targetLeft - offset;
1723320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1724320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                }
1725320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1726320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1727320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (startLeft != targetLeft || startRight != targetRight) {
1728bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes                ValueAnimatorCompat animator = mIndicatorAnimator = ViewUtils.createAnimator();
1729f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
1730f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setDuration(duration);
1731f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setFloatValues(0, 1);
1732f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
1733320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
1734f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
1735f44e88b81821e61a65886cc7027617ca8995070bChris Banes                        final float fraction = animator.getAnimatedFraction();
1736320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        setIndicatorPosition(
1737f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startLeft, targetLeft, fraction),
1738f44e88b81821e61a65886cc7027617ca8995070bChris Banes                                AnimationUtils.lerp(startRight, targetRight, fraction));
1739320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1740f44e88b81821e61a65886cc7027617ca8995070bChris Banes                });
1741f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.setListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
1742320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    @Override
1743f44e88b81821e61a65886cc7027617ca8995070bChris Banes                    public void onAnimationEnd(ValueAnimatorCompat animator) {
1744320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectedPosition = position;
1745320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mSelectionOffset = 0f;
1746320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                    }
1747320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                });
1748f44e88b81821e61a65886cc7027617ca8995070bChris Banes                animator.start();
1749320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1750320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1751320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
1752320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        @Override
1753442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes        public void draw(Canvas canvas) {
1754442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes            super.draw(canvas);
1755442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes
1756320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            // Thick colored underline below the current selection
1757320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
1758320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
1759320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes                        mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
1760320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes            }
1761320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes        }
1762320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes    }
1763320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes
176445cbb1d10c865209f373a148556191700d8ea345Chris Banes    private static ColorStateList createColorStateList(int defaultColor, int selectedColor) {
176545cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[][] states = new int[2][];
176645cbb1d10c865209f373a148556191700d8ea345Chris Banes        final int[] colors = new int[2];
176745cbb1d10c865209f373a148556191700d8ea345Chris Banes        int i = 0;
176845cbb1d10c865209f373a148556191700d8ea345Chris Banes
176945cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = SELECTED_STATE_SET;
177045cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = selectedColor;
177145cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
177245cbb1d10c865209f373a148556191700d8ea345Chris Banes
177345cbb1d10c865209f373a148556191700d8ea345Chris Banes        // Default enabled state
177445cbb1d10c865209f373a148556191700d8ea345Chris Banes        states[i] = EMPTY_STATE_SET;
177545cbb1d10c865209f373a148556191700d8ea345Chris Banes        colors[i] = defaultColor;
177645cbb1d10c865209f373a148556191700d8ea345Chris Banes        i++;
177745cbb1d10c865209f373a148556191700d8ea345Chris Banes
177845cbb1d10c865209f373a148556191700d8ea345Chris Banes        return new ColorStateList(states, colors);
177945cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
178045cbb1d10c865209f373a148556191700d8ea345Chris Banes
1781c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes    private int getDefaultHeight() {
1782c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        boolean hasIconAndText = false;
1783c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        for (int i = 0, count = mTabs.size(); i < count; i++) {
1784c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            Tab tab = mTabs.get(i);
1785c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) {
1786c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                hasIconAndText = true;
1787c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes                break;
1788c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes            }
178945cbb1d10c865209f373a148556191700d8ea345Chris Banes        }
1790c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes        return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT;
179145cbb1d10c865209f373a148556191700d8ea345Chris Banes    }
179245cbb1d10c865209f373a148556191700d8ea345Chris Banes
1793cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private int getTabMinWidth() {
1794cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        if (mRequestedTabMinWidth != INVALID_WIDTH) {
1795cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            // If we have been given a min width, use it
1796cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes            return mRequestedTabMinWidth;
1797cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        }
1798cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        // Else, we'll use the default value
1799cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;
1800cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    }
1801cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1802cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    private int getTabMaxWidth() {
1803cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes        return mTabMaxWidth;
1804cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes    }
1805cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes
1806745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
1807745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link ViewPager.OnPageChangeListener} class which contains the
1808745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * necessary calls back to the provided {@link TabLayout} so that the tab position is
1809745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * kept in sync.
1810745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     *
1811745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * <p>This class stores the provided TabLayout weakly, meaning that you can use
1812745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
1813745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
1814745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * not cause a leak.
1815745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
1816745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
1817745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final WeakReference<TabLayout> mTabLayoutRef;
181814113e918762c4319dfde99e5b87873deeda9f03Chris Banes        private int mPreviousScrollState;
1819745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private int mScrollState;
1820745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1821745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
1822745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mTabLayoutRef = new WeakReference<>(tabLayout);
1823745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1824745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1825745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1826745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onPageScrollStateChanged(int state) {
182714113e918762c4319dfde99e5b87873deeda9f03Chris Banes            mPreviousScrollState = mScrollState;
1828745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mScrollState = state;
1829745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1830745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1831745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
183214113e918762c4319dfde99e5b87873deeda9f03Chris Banes        public void onPageScrolled(int position, float positionOffset,
183314113e918762c4319dfde99e5b87873deeda9f03Chris Banes                int positionOffsetPixels) {
1834745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            final TabLayout tabLayout = mTabLayoutRef.get();
1835745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            if (tabLayout != null) {
183638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                // Only update the text selection if we're not settling, or we are settling after
183738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                // being dragged
183838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
183938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        mPreviousScrollState == SCROLL_STATE_DRAGGING;
18403fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // Update the indicator if we're not settling after being idle. This is caused
18413fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // from a setCurrentItem() call and will be handled by an animation from
18423fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                // onPageSelected() instead.
184338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
184438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        && mPreviousScrollState == SCROLL_STATE_IDLE);
18453fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
1846745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            }
1847745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1848745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1849745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1850745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onPageSelected(int position) {
185114113e918762c4319dfde99e5b87873deeda9f03Chris Banes            final TabLayout tabLayout = mTabLayoutRef.get();
1852289c3d98a7dc9525dbb47b1432f11d4f84cb7fd6Chris Banes            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position) {
185314113e918762c4319dfde99e5b87873deeda9f03Chris Banes                // Select the tab, only updating the indicator if we're not being dragged/settled
185414113e918762c4319dfde99e5b87873deeda9f03Chris Banes                // (since onPageScrolled will handle that).
185538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
185638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        || (mScrollState == SCROLL_STATE_SETTLING
185738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes                        && mPreviousScrollState == SCROLL_STATE_IDLE);
18583fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes                tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
185914113e918762c4319dfde99e5b87873deeda9f03Chris Banes            }
1860745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
186138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
186238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        private void reset() {
186338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
186438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
1865745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
1866745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1867745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    /**
1868745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back
1869745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     * to the provided {@link ViewPager} so that the tab position is kept in sync.
1870745636602593f0b8ae508eb10266a3c7c675e746Chris Banes     */
1871745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
1872745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        private final ViewPager mViewPager;
1873745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1874745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
1875745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager = viewPager;
1876745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1877745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1878745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1879745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabSelected(TabLayout.Tab tab) {
1880745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            mViewPager.setCurrentItem(tab.getPosition());
1881745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1882745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1883745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1884745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabUnselected(TabLayout.Tab tab) {
1885745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
1886745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1887745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
1888745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        @Override
1889745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        public void onTabReselected(TabLayout.Tab tab) {
1890745636602593f0b8ae508eb10266a3c7c675e746Chris Banes            // No-op
1891745636602593f0b8ae508eb10266a3c7c675e746Chris Banes        }
1892745636602593f0b8ae508eb10266a3c7c675e746Chris Banes    }
1893745636602593f0b8ae508eb10266a3c7c675e746Chris Banes
189438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    private class PagerAdapterObserver extends DataSetObserver {
189538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        @Override
189638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public void onChanged() {
189738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            populateFromPagerAdapter();
189838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
189938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
190038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        @Override
190138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        public void onInvalidated() {
190238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes            populateFromPagerAdapter();
190338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes        }
190438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes    }
190538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes
1906320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes}
1907