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