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