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 198e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikasimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 204c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING; 214c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE; 224c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING; 234c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes 24ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikasimport android.animation.Animator; 25ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikasimport android.animation.AnimatorListenerAdapter; 26ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikasimport android.animation.ValueAnimator; 27320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.Context; 28320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.ColorStateList; 29c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.content.res.Resources; 30320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.content.res.TypedArray; 3138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.database.DataSetObserver; 32320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Canvas; 33320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.Paint; 34320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.graphics.drawable.Drawable; 35320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.os.Build; 3684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banesimport android.support.annotation.ColorInt; 3740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.DrawableRes; 38320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.annotation.IntDef; 3940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.LayoutRes; 4040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.NonNull; 4140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.Nullable; 42c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo; 4340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banesimport android.support.annotation.StringRes; 44320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.design.R; 4538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banesimport android.support.v4.util.Pools; 46320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.GravityCompat; 47320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.PagerAdapter; 48155d8e15c8f1b959196c16555c54d19a5ccafe99Vladislav Kaznacheevimport android.support.v4.view.PointerIconCompat; 49320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewCompat; 50320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v4.view.ViewPager; 51092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banesimport android.support.v4.widget.TextViewCompat; 52320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.support.v7.app.ActionBar; 534c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banesimport android.support.v7.content.res.AppCompatResources; 54a4c6825b4b077cb12adb4f14b825001b6fd0e865Vladislav Kaznacheevimport android.support.v7.widget.TooltipCompat; 55c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.text.Layout; 56320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.text.TextUtils; 57320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.util.AttributeSet; 58c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banesimport android.util.TypedValue; 59320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.Gravity; 60320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.LayoutInflater; 61141f5ae472feb06dc6059f02cab9e88791d30449Alan Viveretteimport android.view.SoundEffectConstants; 62320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.View; 63320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewGroup; 64320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.ViewParent; 65320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityEvent; 66320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.view.accessibility.AccessibilityNodeInfo; 67320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.HorizontalScrollView; 68320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.ImageView; 69320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.LinearLayout; 70320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport android.widget.TextView; 71320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 72320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.Retention; 73320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.lang.annotation.RetentionPolicy; 74745636602593f0b8ae508eb10266a3c7c675e746Chris Banesimport java.lang.ref.WeakReference; 75320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.ArrayList; 76320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banesimport java.util.Iterator; 77320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 78320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes/** 799fb154338a62edc2c57dc036895199d6f1769400Chris Banes * TabLayout provides a horizontal layout to display tabs. 809fb154338a62edc2c57dc036895199d6f1769400Chris Banes * 819fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Population of the tabs to display is 82320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can 83320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)} 849fb154338a62edc2c57dc036895199d6f1769400Chris Banes * respectively. To display the tab, you need to add it to the layout via one of the 859fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #addTab(Tab)} methods. For example: 86320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <pre> 87320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * TabLayout tabLayout = ...; 88320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 1")); 89320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 2")); 90320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * tabLayout.addTab(tabLayout.newTab().setText("Tab 3")); 91320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </pre> 92320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be 93320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * notified when any tab's selection state has been changed. 94320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 954cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <p>You can also add items to TabLayout in your layout through the use of {@link TabItem}. 964cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * An example usage is like so:</p> 974cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 984cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <pre> 994cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <android.support.design.widget.TabLayout 1004cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * android:layout_height="wrap_content" 1014cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * android:layout_width="match_parent"> 1024cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 1034cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <android.support.design.widget.TabItem 1044cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * android:text="@string/tab_text"/> 1054cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 1064cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * <android.support.design.widget.TabItem 1074cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * android:icon="@drawable/ic_android"/> 1084cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 1094cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * </android.support.design.widget.TabLayout> 1104cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * </pre> 1114cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 1128f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <h3>ViewPager integration</h3> 1138f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <p> 1148f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * If you're using a {@link android.support.v4.view.ViewPager} together 1158f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together. 1168f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p> 1178f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * 1188f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <p> 1198f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * This view also supports being used as part of a ViewPager's decor, and can be added 1208f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * directly to the ViewPager in a layout resource file like so:</p> 1218f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * 1228f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <pre> 1238f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <android.support.v4.view.ViewPager 1248f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * android:layout_width="match_parent" 1258f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * android:layout_height="match_parent"> 1268f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * 1278f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * <android.support.design.widget.TabLayout 1288f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * android:layout_width="match_parent" 1298f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * android:layout_height="wrap_content" 1308f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * android:layout_gravity="top" /> 1318f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * 1328f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * </android.support.v4.view.ViewPager> 1338f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes * </pre> 1344cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 135320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a> 1364cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 1374cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPadding 1384cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart 1394cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop 1404cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd 1414cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom 1424cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart 1434cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabBackground 1444cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth 1454cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth 1464cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance 147320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1488f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes@ViewPager.DecorView 149320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banespublic class TabLayout extends HorizontalScrollView { 150320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 151c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps 152657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas static final int DEFAULT_GAP_TEXT_ICON = 8; // dps 153cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes private static final int INVALID_WIDTH = -1; 154320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private static final int DEFAULT_HEIGHT = 48; // dps 155320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps 156657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas static final int FIXED_WRAP_GUTTER_MIN = 16; //dps 157657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas static final int MOTION_NON_ADJACENT_OFFSET = 24; 158320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 159320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private static final int ANIMATION_DURATION = 300; 160320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 16138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<>(16); 16238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 163320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 164320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab 165320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * labels and a larger number of tabs. They are best used for browsing contexts in touch 166320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * interfaces when users don’t need to directly compare the tab labels. 167320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 168320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setTabMode(int) 169320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getTabMode() 170320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 171320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final int MODE_SCROLLABLE = 0; 172320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 173320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 174320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Fixed tabs display all tabs concurrently and are best used with content that benefits from 175320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. 176320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Fixed tabs have equal width, based on the widest tab label. 177320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setTabMode(int) 179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getTabMode() 180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final int MODE_FIXED = 1; 182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @hide 185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1868e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas @RestrictTo(LIBRARY_GROUP) 187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED}) 188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Retention(RetentionPolicy.SOURCE) 189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public @interface Mode {} 190320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 191320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 192320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect 193320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * when used with {@link #MODE_FIXED}. 194320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 195320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setTabGravity(int) 196320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getTabGravity() 197320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final int GRAVITY_FILL = 0; 199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 200320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Gravity used to lay out the tabs in the center of the {@link TabLayout}. 202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setTabGravity(int) 204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getTabGravity() 205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final int GRAVITY_CENTER = 1; 207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @hide 210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 2118e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas @RestrictTo(LIBRARY_GROUP) 212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER}) 213320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Retention(RetentionPolicy.SOURCE) 214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public @interface TabGravity {} 215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Callback interface invoked when a tab's selection state changes. 218320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 219320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public interface OnTabSelectedListener { 220320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 222320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Called when a tab enters the selected state. 223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab that was selected 225320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void onTabSelected(Tab tab); 227320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 228320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 229320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Called when a tab exits the selected state. 230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab that was unselected 232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void onTabUnselected(Tab tab); 234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Called when a tab that is already selected is chosen again by the user. Some applications 237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * may use this action to return to the top level of a category. 238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 239320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab that was reselected. 240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 241320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void onTabReselected(Tab tab); 242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 244320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private final ArrayList<Tab> mTabs = new ArrayList<>(); 245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private Tab mSelectedTab; 246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private final SlidingTabStrip mTabStrip; 248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 249657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabPaddingStart; 250657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabPaddingTop; 251657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabPaddingEnd; 252657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabPaddingBottom; 253320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 254657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabTextAppearance; 255657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas ColorStateList mTabTextColors; 256657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas float mTabTextSize; 257657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas float mTabTextMultiLineSize; 25845cbb1d10c865209f373a148556191700d8ea345Chris Banes 259657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas final int mTabBackgroundResId; 260320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 261657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabMaxWidth = Integer.MAX_VALUE; 262cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes private final int mRequestedTabMinWidth; 263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private final int mRequestedTabMaxWidth; 264cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes private final int mScrollableTabMinWidth; 265320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 266320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int mContentInsetStart; 267320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 268657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mTabGravity; 269657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mMode; 270320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 2716f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private OnTabSelectedListener mSelectedListener; 2726f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>(); 2736f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private OnTabSelectedListener mCurrentVpSelectedListener; 274320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 275ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas private ValueAnimator mScrollAnimator; 276bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes 277657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas ViewPager mViewPager; 27838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private PagerAdapter mPagerAdapter; 27938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private DataSetObserver mPagerAdapterObserver; 28038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private TabLayoutOnPageChangeListener mPageChangeListener; 2818f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes private AdapterChangeListener mAdapterChangeListener; 2828f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes private boolean mSetupViewPagerImplicitly; 28338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 28438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Pool we use as a simple RecyclerBin 28538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12); 28638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 287320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public TabLayout(Context context) { 288320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes this(context, null); 289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 291320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public TabLayout(Context context, AttributeSet attrs) { 292b3ba94bf8b5cdae24e5a09a83813d72f2e2d8c1aChris Banes this(context, attrs, 0); 293320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 294320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 295320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 296320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super(context, attrs, defStyleAttr); 297320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 298809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes ThemeUtils.checkAppCompatTheme(context); 299809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes 300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Disable the Scroll Bar 301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes setHorizontalScrollBarEnabled(false); 302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Add the TabStrip 304320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip = new SlidingTabStrip(context); 3054cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams( 3064cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); 307320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 308320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout, 309320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes defStyleAttr, R.style.Widget_Design_TabLayout); 310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 311320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip.setSelectedIndicatorHeight( 312320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0)); 313320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0)); 314320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 315320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a 316320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); 317320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, 318320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingStart); 319320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop, 320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingTop); 321320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, 322320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingEnd); 323320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom, 324320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingBottom); 325320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 326c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance, 327c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes R.style.TextAppearance_Design_Tab); 328c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 329c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // Text colors/sizes come from the text appearance first 330c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance, 3316759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski android.support.v7.appcompat.R.styleable.TextAppearance); 332c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes try { 3336759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski mTabTextSize = ta.getDimensionPixelSize( 3346759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0); 3356759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski mTabTextColors = ta.getColorStateList( 3366759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor); 337c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } finally { 338c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes ta.recycle(); 339c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 34045cbb1d10c865209f373a148556191700d8ea345Chris Banes 34145cbb1d10c865209f373a148556191700d8ea345Chris Banes if (a.hasValue(R.styleable.TabLayout_tabTextColor)) { 34245cbb1d10c865209f373a148556191700d8ea345Chris Banes // If we have an explicit text color set, use it instead 34345cbb1d10c865209f373a148556191700d8ea345Chris Banes mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor); 34445cbb1d10c865209f373a148556191700d8ea345Chris Banes } 34545cbb1d10c865209f373a148556191700d8ea345Chris Banes 346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) { 34745cbb1d10c865209f373a148556191700d8ea345Chris Banes // We have an explicit selected text color set, so we need to make merge it with the 34845cbb1d10c865209f373a148556191700d8ea345Chris Banes // current colors. This is exposed so that developers can use theme attributes to set 34945cbb1d10c865209f373a148556191700d8ea345Chris Banes // this (theme attrs in ColorStateLists are Lollipop+) 35045cbb1d10c865209f373a148556191700d8ea345Chris Banes final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0); 35145cbb1d10c865209f373a148556191700d8ea345Chris Banes mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected); 352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 354cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, 355cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes INVALID_WIDTH); 356cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, 357cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes INVALID_WIDTH); 358320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0); 359320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0); 360320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED); 361320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL); 362320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes a.recycle(); 363320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 364cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // TODO add attr for these 365cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes final Resources res = getResources(); 366cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line); 367cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width); 368c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 369320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Now apply the tab mode and gravity 370320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes applyModeAndGravity(); 371320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 373320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 37484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * Sets the tab indicator's color for the currently selected tab. 37584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * 37684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * @param color color to use for the indicator 3774cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 3784cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor 37984a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes */ 38084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes public void setSelectedTabIndicatorColor(@ColorInt int color) { 38184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes mTabStrip.setSelectedIndicatorColor(color); 38284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes } 38384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes 38484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes /** 38584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * Sets the tab indicator's height for the currently selected tab. 38684a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * 38784a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes * @param height height to use for the indicator in pixels 3884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 3894cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight 39084a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes */ 39184a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes public void setSelectedTabIndicatorHeight(int height) { 39284a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes mTabStrip.setSelectedIndicatorHeight(height); 39384a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes } 39484a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes 39584a51515c4b2d8b7b8dd619e158589c78048adf9Chris Banes /** 396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as 397745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * part of a scrolling container such as {@link android.support.v4.view.ViewPager}. 398320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <p> 399320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Calling this method does not update the selected tab, it is only used for drawing purposes. 400ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes * 401ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes * @param position current scroll position 402ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes * @param positionOffset Value from [0, 1) indicating the offset from {@code position}. 403ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes * @param updateSelectedText Whether to update the text's selected state. 404320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 405ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) { 4063fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes setScrollPosition(position, positionOffset, updateSelectedText, true); 4073fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes } 4083fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes 409657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void setScrollPosition(int position, float positionOffset, boolean updateSelectedText, 4103fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes boolean updateIndicatorPosition) { 4113fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes final int roundedPosition = Math.round(position + positionOffset); 4123fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) { 413320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return; 414320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 4153fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes 4163fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // Set the indicator position, if enabled 4173fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (updateIndicatorPosition) { 4183fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset); 419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 4213fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // Now update the scroll position, canceling any running animation 4223fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (mScrollAnimator != null && mScrollAnimator.isRunning()) { 4233fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes mScrollAnimator.cancel(); 4243fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes } 425320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes scrollTo(calculateScrollXForTab(position, positionOffset), 0); 426320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 4273fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // Update the 'selected state' view as we scroll, if enabled 428ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes if (updateSelectedText) { 4293fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes setSelectedTabView(roundedPosition); 430ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes } 431320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 432320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 43309db0f00a693704eb747027def4279362c51894eChris Banes private float getScrollPosition() { 43409db0f00a693704eb747027def4279362c51894eChris Banes return mTabStrip.getIndicatorPosition(); 43509db0f00a693704eb747027def4279362c51894eChris Banes } 43609db0f00a693704eb747027def4279362c51894eChris Banes 437320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 438320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Add a tab to this layout. The tab will be added at the end of the list. 439320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * If this is the first tab to be added it will become the selected tab. 440320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab Tab to add 442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 44340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public void addTab(@NonNull Tab tab) { 444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes addTab(tab, mTabs.isEmpty()); 445320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 447320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Add a tab to this layout. The tab will be inserted at <code>position</code>. 449320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * If this is the first tab to be added it will become the selected tab. 450320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 451320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab to add 452320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param position The new position of the tab 453320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 45440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public void addTab(@NonNull Tab tab, int position) { 455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes addTab(tab, position, mTabs.isEmpty()); 456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Add a tab to this layout. The tab will be added at the end of the list. 460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab Tab to add 462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param setSelected True if the added tab should become the selected tab. 463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 46440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public void addTab(@NonNull Tab tab, boolean setSelected) { 465ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes addTab(tab, mTabs.size(), setSelected); 466320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 467320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 469320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Add a tab to this layout. The tab will be inserted at <code>position</code>. 470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab to add 472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param position The new position of the tab 473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param setSelected True if the added tab should become the selected tab. 474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 47540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public void addTab(@NonNull Tab tab, int position, boolean setSelected) { 476320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (tab.mParent != this) { 477320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes throw new IllegalArgumentException("Tab belongs to a different TabLayout."); 478320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 479320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes configureTab(tab, position); 480ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes addTabView(tab); 481ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes 482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (setSelected) { 483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes tab.select(); 484320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 485320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 486320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 4874cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes private void addTabFromItemView(@NonNull TabItem item) { 4884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes final Tab tab = newTab(); 4894cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes if (item.mText != null) { 4904cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes tab.setText(item.mText); 4914cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 4924cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes if (item.mIcon != null) { 4934cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes tab.setIcon(item.mIcon); 4944cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 4954cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes if (item.mCustomLayout != 0) { 4964cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes tab.setCustomView(item.mCustomLayout); 4974cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 4983be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes if (!TextUtils.isEmpty(item.getContentDescription())) { 4993be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes tab.setContentDescription(item.getContentDescription()); 5003be5f23bbe512ce9c4b073efbdcbac132a737787Chris Banes } 5014cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addTab(tab); 5024cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 5034cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 504320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 5056f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and 5066f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}. 5076f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes */ 5086f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes @Deprecated 5096f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) { 5106f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes // The logic in this method emulates what we had before support for multiple 5116f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes // registered listeners. 5126f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (mSelectedListener != null) { 5136f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes removeOnTabSelectedListener(mSelectedListener); 5146f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 5156f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes // Update the deprecated field so that we can remove the passed listener the next 5166f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes // time we're called 5176f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListener = listener; 5186f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (listener != null) { 5196f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes addOnTabSelectedListener(listener); 5206f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 5216f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 5226f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 5236f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes /** 5246f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection 5256f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * changes. 5266f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * 5276f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * <p>Components that add a listener should take care to remove it when finished via 5286f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p> 529320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 5306f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * @param listener listener to add 531320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 5326f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { 5336f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (!mSelectedListeners.contains(listener)) { 5346f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListeners.add(listener); 5356f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 5366f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 5376f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 5386f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes /** 5396f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via 5406f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * {@link #addOnTabSelectedListener(OnTabSelectedListener)}. 5416f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * 5426f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes * @param listener listener to remove 5436f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes */ 5446f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { 5456f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListeners.remove(listener); 546320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 547320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 548320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 549c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes * Remove all previously added {@link TabLayout.OnTabSelectedListener}s. 550c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes */ 551c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes public void clearOnTabSelectedListeners() { 552c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes mSelectedListeners.clear(); 553c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes } 554c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes 555c26cfaff58b502418b31f08399ac6a770bd0181cChris Banes /** 556320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Create and return a new {@link Tab}. You need to manually add this using 557320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * {@link #addTab(Tab)} or a related method. 558320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 559320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return A new Tab 560320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #addTab(Tab) 561320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 56240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 563320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public Tab newTab() { 5643a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes Tab tab = sTabPool.acquire(); 5653a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes if (tab == null) { 56650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes tab = new Tab(); 5673a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } 56850f777a07774d0f24529e2888fcba95744cb0be1Chris Banes tab.mParent = this; 5693a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes tab.mView = createTabView(tab); 5703a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes return tab; 571320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 572320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 573320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 574320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Returns the number of tabs currently registered with the action bar. 575320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 576320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return Tab count 577320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 578320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public int getTabCount() { 579320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mTabs.size(); 580320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 581320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 582320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 583320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Returns the tab at the specified index. 584320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 58540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 586320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public Tab getTabAt(int index) { 58763305fa8581f5e5430df94ef23027409eb941a8dAurimas Liutikas return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index); 588320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 589320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 590320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 5911a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes * Returns the position of the current selected tab. 5921a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes * 5931a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes * @return selected tab position, or {@code -1} if there isn't a selected tab. 5941a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes */ 5951a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes public int getSelectedTabPosition() { 5961a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes return mSelectedTab != null ? mSelectedTab.getPosition() : -1; 5971a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes } 5981a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes 5991a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes /** 600320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Remove a tab from the layout. If the removed tab was selected it will be deselected 601320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * and another tab will be selected if present. 602320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 603320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tab The tab to remove 604320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 605320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void removeTab(Tab tab) { 606320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (tab.mParent != this) { 607320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes throw new IllegalArgumentException("Tab does not belong to this TabLayout."); 608320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 609320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 610320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes removeTabAt(tab.getPosition()); 611320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 612320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 613320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 614320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Remove a tab from the layout. If the removed tab was selected it will be deselected 615320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * and another tab will be selected if present. 616320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 617320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param position Position of the tab to remove 618320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 619320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void removeTabAt(int position) { 620320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; 621320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes removeTabViewAt(position); 622320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 62338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final Tab removedTab = mTabs.remove(position); 624320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (removedTab != null) { 62538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes removedTab.reset(); 62638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes sTabPool.release(removedTab); 627320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 628320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 629320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int newTabCount = mTabs.size(); 630320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes for (int i = position; i < newTabCount; i++) { 631320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabs.get(i).setPosition(i); 632320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 633320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 634320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (selectedTabPosition == position) { 635320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); 636320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 637320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 638320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 639320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 640320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Remove all tabs from the action bar and deselect the current tab. 641320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 642320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void removeAllTabs() { 643320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Remove all the views 64438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) { 64538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes removeTabViewAt(i); 64638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 647320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 64838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes for (final Iterator<Tab> i = mTabs.iterator(); i.hasNext();) { 64938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final Tab tab = i.next(); 650320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes i.remove(); 65138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes tab.reset(); 65238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes sTabPool.release(tab); 653320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 654bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes 655bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes mSelectedTab = null; 656320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 657320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 658320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 659320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the behavior mode for the Tabs in this layout. The valid input options are: 660320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <ul> 661320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used 662320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * with content that benefits from quick pivots between tabs.</li> 663320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment, 664320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * and can contain longer tab labels and a larger number of tabs. They are best used for 665320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * browsing contexts in touch interfaces when users don’t need to directly compare the tab 666745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li> 667320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * </ul> 668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}. 6704cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 6714cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabMode 672320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 673320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void setTabMode(@Mode int mode) { 674320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mode != mMode) { 675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mMode = mode; 676320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes applyModeAndGravity(); 677320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 678320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 679320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 680320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 681320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Returns the current mode used by this {@link TabLayout}. 682320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 683320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setTabMode(int) 684320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 685320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Mode 686320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public int getTabMode() { 687320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mMode; 688320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 689320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 690320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 691320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the gravity to use when laying out the tabs. 692320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 693320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. 6944cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 6954cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabGravity 696320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 697320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void setTabGravity(@TabGravity int gravity) { 698320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mTabGravity != gravity) { 699320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabGravity = gravity; 700320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes applyModeAndGravity(); 701320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 702320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 703320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 704320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 705320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * The current gravity used for laying out tabs. 706320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 707320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. 708320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 709320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @TabGravity 710320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public int getTabGravity() { 711320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mTabGravity; 712320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 713320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 714320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 71545cbb1d10c865209f373a148556191700d8ea345Chris Banes * Sets the text colors for the different states (normal, selected) used for the tabs. 7164cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 7174cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @see #getTabTextColors() 718320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 71940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public void setTabTextColors(@Nullable ColorStateList textColor) { 72045cbb1d10c865209f373a148556191700d8ea345Chris Banes if (mTabTextColors != textColor) { 72145cbb1d10c865209f373a148556191700d8ea345Chris Banes mTabTextColors = textColor; 72245cbb1d10c865209f373a148556191700d8ea345Chris Banes updateAllTabs(); 723320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 724320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 725320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 726320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 72745cbb1d10c865209f373a148556191700d8ea345Chris Banes * Gets the text colors for the different states (normal, selected) used for the tabs. 728320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 72940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 73045cbb1d10c865209f373a148556191700d8ea345Chris Banes public ColorStateList getTabTextColors() { 73145cbb1d10c865209f373a148556191700d8ea345Chris Banes return mTabTextColors; 73245cbb1d10c865209f373a148556191700d8ea345Chris Banes } 73345cbb1d10c865209f373a148556191700d8ea345Chris Banes 73445cbb1d10c865209f373a148556191700d8ea345Chris Banes /** 73545cbb1d10c865209f373a148556191700d8ea345Chris Banes * Sets the text colors for the different states (normal, selected) used for the tabs. 7364cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * 7374cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor 7384cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor 73945cbb1d10c865209f373a148556191700d8ea345Chris Banes */ 74045cbb1d10c865209f373a148556191700d8ea345Chris Banes public void setTabTextColors(int normalColor, int selectedColor) { 74145cbb1d10c865209f373a148556191700d8ea345Chris Banes setTabTextColors(createColorStateList(normalColor, selectedColor)); 74245cbb1d10c865209f373a148556191700d8ea345Chris Banes } 74345cbb1d10c865209f373a148556191700d8ea345Chris Banes 744745636602593f0b8ae508eb10266a3c7c675e746Chris Banes /** 745745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. 746745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * 7473cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with 7483cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * auto-refresh enabled.</p> 7493cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * 7503cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link 7513cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes */ 7523cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes public void setupWithViewPager(@Nullable ViewPager viewPager) { 7533cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes setupWithViewPager(viewPager, true); 7543cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes } 7553cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes 7563cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes /** 7573cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. 7583cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * 7593cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * <p>This method will link the given ViewPager and this TabLayout together so that 7603cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * changes in one are automatically reflected in the other. This includes scroll state changes 7613cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * and clicks. The tabs displayed in this layout will be populated 76238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * from the ViewPager adapter's page titles.</p> 76338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * 7643cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will 7653cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * trigger this layout to re-populate itself from the adapter's titles.</p> 766745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * 76738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * <p>If the given ViewPager is non-null, it needs to already have a 76838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * {@link PagerAdapter} set.</p> 76938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * 7703cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link 7713cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's 7723cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes * content changes 773745636602593f0b8ae508eb10266a3c7c675e746Chris Banes */ 7743cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) { 7758f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes setupWithViewPager(viewPager, autoRefresh, false); 7768f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 7778f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 7788f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, 7798f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes boolean implicitSetup) { 7808f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mViewPager != null) { 78138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // If we've already been setup with a ViewPager, remove us from it 7828f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mPageChangeListener != null) { 7838f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mViewPager.removeOnPageChangeListener(mPageChangeListener); 7848f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 7858f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mAdapterChangeListener != null) { 7868f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener); 7878f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 788745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 789745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 7906f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (mCurrentVpSelectedListener != null) { 7916f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes // If we already have a tab selected listener for the ViewPager, remove it 7926f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes removeOnTabSelectedListener(mCurrentVpSelectedListener); 7936f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mCurrentVpSelectedListener = null; 7946f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 7956f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 79638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (viewPager != null) { 79738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mViewPager = viewPager; 798112f648090b48c0d00a51983baf148dbbc800abbChris Banes 79938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Add our custom OnPageChangeListener to the ViewPager 80038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (mPageChangeListener == null) { 80138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPageChangeListener = new TabLayoutOnPageChangeListener(this); 80209db0f00a693704eb747027def4279362c51894eChris Banes } 80338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPageChangeListener.reset(); 80438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes viewPager.addOnPageChangeListener(mPageChangeListener); 80538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 80638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Now we'll add a tab selected listener to set ViewPager's current item 8076f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); 8086f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes addOnTabSelectedListener(mCurrentVpSelectedListener); 80938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 8108f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes final PagerAdapter adapter = viewPager.getAdapter(); 8118f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (adapter != null) { 8128f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // Now we'll populate ourselves from the pager adapter, adding an observer if 8138f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // autoRefresh is enabled 8148f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes setPagerAdapter(adapter, autoRefresh); 8158f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8168f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 8178f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // Add a listener so that we're notified of any adapter changes 8188f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mAdapterChangeListener == null) { 8198f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mAdapterChangeListener = new AdapterChangeListener(); 8208f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8218f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mAdapterChangeListener.setAutoRefresh(autoRefresh); 8228f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes viewPager.addOnAdapterChangeListener(mAdapterChangeListener); 823617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes 824617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes // Now update the scroll position to match the ViewPager's current item 825617471b5d3c1465056c542de6343812cc6d6cd06Chris Banes setScrollPosition(viewPager.getCurrentItem(), 0f, true); 82638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } else { 82738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // We've been given a null ViewPager so we need to clear out the internal state, 82838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // listeners and observers 82938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mViewPager = null; 8303cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes setPagerAdapter(null, false); 831112f648090b48c0d00a51983baf148dbbc800abbChris Banes } 8328f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 8338f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mSetupViewPagerImplicitly = implicitSetup; 834745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 835745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 836745636602593f0b8ae508eb10266a3c7c675e746Chris Banes /** 83738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a TabLayout with a ViewPager 83838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * together. When that method is used, the TabLayout will be automatically updated 83938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes * when the {@link PagerAdapter} is changed. 840745636602593f0b8ae508eb10266a3c7c675e746Chris Banes */ 84138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes @Deprecated 84238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes public void setTabsFromPagerAdapter(@Nullable final PagerAdapter adapter) { 84338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes setPagerAdapter(adapter, false); 84438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 84538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 846090d6780d4a901b258f33bcaa53014ca06744857Chris Banes @Override 847090d6780d4a901b258f33bcaa53014ca06744857Chris Banes public boolean shouldDelayChildPressedState() { 848090d6780d4a901b258f33bcaa53014ca06744857Chris Banes // Only delay the pressed state if the tabs can scroll 849090d6780d4a901b258f33bcaa53014ca06744857Chris Banes return getTabScrollRange() > 0; 850090d6780d4a901b258f33bcaa53014ca06744857Chris Banes } 851090d6780d4a901b258f33bcaa53014ca06744857Chris Banes 8528f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes @Override 8538f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes protected void onAttachedToWindow() { 8548f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes super.onAttachedToWindow(); 8558f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 8568f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mViewPager == null) { 8578f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // If we don't have a ViewPager already, check if our parent is a ViewPager to 8588f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // setup with it automatically 8598f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes final ViewParent vp = getParent(); 8608f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (vp instanceof ViewPager) { 8618f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // If we have a ViewPager parent and we've been added as part of its decor, let's 8628f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // assume that we should automatically setup to display any titles 8638f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes setupWithViewPager((ViewPager) vp, true, true); 8648f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8658f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8668f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8678f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 8688f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes @Override 8698f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes protected void onDetachedFromWindow() { 8708f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes super.onDetachedFromWindow(); 8718f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 8728f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mSetupViewPagerImplicitly) { 8738f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc 8748f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes setupWithViewPager(null); 8758f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mSetupViewPagerImplicitly = false; 8768f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8778f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 8788f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 879090d6780d4a901b258f33bcaa53014ca06744857Chris Banes private int getTabScrollRange() { 880090d6780d4a901b258f33bcaa53014ca06744857Chris Banes return Math.max(0, mTabStrip.getWidth() - getWidth() - getPaddingLeft() 881090d6780d4a901b258f33bcaa53014ca06744857Chris Banes - getPaddingRight()); 882090d6780d4a901b258f33bcaa53014ca06744857Chris Banes } 883090d6780d4a901b258f33bcaa53014ca06744857Chris Banes 884657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) { 88538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (mPagerAdapter != null && mPagerAdapterObserver != null) { 88638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // If we already have a PagerAdapter, unregister our observer 88738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); 88838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 88938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 89038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPagerAdapter = adapter; 89138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 89238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (addObserver && adapter != null) { 89338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Register our observer on the new adapter 89438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (mPagerAdapterObserver == null) { 89538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPagerAdapterObserver = new PagerAdapterObserver(); 89638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 89738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes adapter.registerDataSetObserver(mPagerAdapterObserver); 89838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 89938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 90038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Finally make sure we reflect the new adapter 90138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes populateFromPagerAdapter(); 90238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 90338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 904657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void populateFromPagerAdapter() { 905745636602593f0b8ae508eb10266a3c7c675e746Chris Banes removeAllTabs(); 90638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 90738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (mPagerAdapter != null) { 90838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final int adapterCount = mPagerAdapter.getCount(); 90938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes for (int i = 0; i < adapterCount; i++) { 91038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); 91138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 91238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 91338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Make sure we reflect the currently set ViewPager item 91438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (mViewPager != null && adapterCount > 0) { 91538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final int curItem = mViewPager.getCurrentItem(); 91638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { 91738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes selectTab(getTabAt(curItem)); 91838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 91938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 920745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 921745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 922745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 92345cbb1d10c865209f373a148556191700d8ea345Chris Banes private void updateAllTabs() { 9243a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes for (int i = 0, z = mTabs.size(); i < z; i++) { 9253a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes mTabs.get(i).updateView(); 92645cbb1d10c865209f373a148556191700d8ea345Chris Banes } 927320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 928320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 92938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private TabView createTabView(@NonNull final Tab tab) { 93038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null; 93138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (tabView == null) { 93238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes tabView = new TabView(getContext()); 93338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 93438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes tabView.setTab(tab); 935320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes tabView.setFocusable(true); 936cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes tabView.setMinimumWidth(getTabMinWidth()); 937320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return tabView; 938320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 939320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 940320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void configureTab(Tab tab, int position) { 941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes tab.setPosition(position); 942320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabs.add(position, tab); 943320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 944320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int count = mTabs.size(); 945320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes for (int i = position + 1; i < count; i++) { 946320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabs.get(i).setPosition(i); 947320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 948320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 949320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 950ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes private void addTabView(Tab tab) { 9513a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes final TabView tabView = tab.mView; 952ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs()); 953320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 954320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 9554cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes @Override 9564cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes public void addView(View child) { 9574cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addViewInternal(child); 9584cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9594cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 9604cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes @Override 9614cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes public void addView(View child, int index) { 9624cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addViewInternal(child); 9634cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9644cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 9654cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes @Override 9664cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes public void addView(View child, ViewGroup.LayoutParams params) { 9674cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addViewInternal(child); 9684cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9694cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 9704cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes @Override 9714cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes public void addView(View child, int index, ViewGroup.LayoutParams params) { 9724cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addViewInternal(child); 9734cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9744cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 9754cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes private void addViewInternal(final View child) { 9764cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes if (child instanceof TabItem) { 9774cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes addTabFromItemView((TabItem) child); 9784cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } else { 9794cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout"); 9804cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9814cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 9824cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 983320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private LinearLayout.LayoutParams createLayoutParamsForTabs() { 984320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 985320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 986320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes updateTabViewLayoutParams(lp); 987320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return lp; 988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 990320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { 991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { 992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes lp.width = 0; 993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes lp.weight = 1; 994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes lp.weight = 0; 997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1000657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int dpToPx(int dps) { 1001320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return Math.round(getResources().getDisplayMetrics().density * dps); 1002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1006320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If we have a MeasureSpec which allows us to decide our height, try and use the default 1007320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // height 1008c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom(); 1009320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes switch (MeasureSpec.getMode(heightMeasureSpec)) { 1010320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes case MeasureSpec.AT_MOST: 1011320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes heightMeasureSpec = MeasureSpec.makeMeasureSpec( 1012bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)), 1013320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes MeasureSpec.EXACTLY); 1014320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes break; 1015320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes case MeasureSpec.UNSPECIFIED: 1016bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY); 1017320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes break; 1018320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1019320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1020cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes final int specWidth = MeasureSpec.getSize(widthMeasureSpec); 1021cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { 1022cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // If we don't have an unspecified width spec, use the given size to calculate 1023cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // the max tab width 1024cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes mTabMaxWidth = mRequestedTabMaxWidth > 0 1025cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes ? mRequestedTabMaxWidth 1026cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN); 1027cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1028cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes 1029320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Now super measure itself using the (possibly) modified height spec 1030320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1031320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1032cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (getChildCount() == 1) { 1033320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If we're in fixed mode then we need to make the tab strip is the same width as us 1034320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // so we don't scroll 1035320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final View child = getChildAt(0); 1036cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes boolean remeasure = false; 1037cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes 1038cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes switch (mMode) { 1039cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes case MODE_SCROLLABLE: 1040cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // We only need to resize the child if it's smaller than us. This is similar 1041cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // to fillViewport 1042cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes remeasure = child.getMeasuredWidth() < getMeasuredWidth(); 1043cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes break; 1044cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes case MODE_FIXED: 1045cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // Resize the child so that it doesn't scroll 1046cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes remeasure = child.getMeasuredWidth() != getMeasuredWidth(); 1047cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes break; 1048cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1049320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1050cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (remeasure) { 1051cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // Re-measure the child with a widthSpec set to be exactly our measure width 1052320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() 1053320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes + getPaddingBottom(), child.getLayoutParams().height); 1054cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( 1055cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes getMeasuredWidth(), MeasureSpec.EXACTLY); 1056320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 1057320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1058320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1059320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1060320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1061320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void removeTabViewAt(int position) { 106238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final TabView view = (TabView) mTabStrip.getChildAt(position); 1063320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip.removeViewAt(position); 106438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (view != null) { 106538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes view.reset(); 106638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mTabViewPool.release(view); 106738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 1068320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes requestLayout(); 1069320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1070320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1071320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void animateToTab(int newPosition) { 1072320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (newPosition == Tab.INVALID_POSITION) { 1073320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return; 1074320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 10757c71281dc4879e7889c65283aabaeed68521fb14Alan Jeon 1076bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes if (getWindowToken() == null || !ViewCompat.isLaidOut(this) 1077bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes || mTabStrip.childrenNeedLayout()) { 1078320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If we don't have a window token, or we haven't been laid out yet just draw the new 1079320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // position now 1080ccfefa26b718c232ec8e3095e05dab75b963f918Chris Banes setScrollPosition(newPosition, 0f, true); 1081320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return; 1082320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1083320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1084320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int startScrollX = getScrollX(); 1085320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int targetScrollX = calculateScrollXForTab(newPosition, 0); 1086320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1087320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (startScrollX != targetScrollX) { 108886d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren ensureScrollAnimator(); 1089bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes 1090bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes mScrollAnimator.setIntValues(startScrollX, targetScrollX); 1091bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes mScrollAnimator.start(); 1092320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1093320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1094320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Now animate the indicator 1095bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); 1096320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1097320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 109886d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren private void ensureScrollAnimator() { 109986d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren if (mScrollAnimator == null) { 11006c3fec5f2bc62c363c459b97bdd39d91631a93fcJonathan Koren mScrollAnimator = new ValueAnimator(); 110186d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 110286d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren mScrollAnimator.setDuration(ANIMATION_DURATION); 11036c3fec5f2bc62c363c459b97bdd39d91631a93fcJonathan Koren mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 110486d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren @Override 11056c3fec5f2bc62c363c459b97bdd39d91631a93fcJonathan Koren public void onAnimationUpdate(ValueAnimator animator) { 11066c3fec5f2bc62c363c459b97bdd39d91631a93fcJonathan Koren scrollTo((int) animator.getAnimatedValue(), 0); 110786d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren } 110886d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren }); 110986d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren } 111086d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren } 111186d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren 11126c3fec5f2bc62c363c459b97bdd39d91631a93fcJonathan Koren void setScrollAnimatorListener(Animator.AnimatorListener listener) { 111386d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren ensureScrollAnimator(); 111486d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren mScrollAnimator.addListener(listener); 111586d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren } 111686d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren 1117320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void setSelectedTabView(int position) { 1118320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int tabCount = mTabStrip.getChildCount(); 1119ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes if (position < tabCount) { 112009db0f00a693704eb747027def4279362c51894eChris Banes for (int i = 0; i < tabCount; i++) { 112109db0f00a693704eb747027def4279362c51894eChris Banes final View child = mTabStrip.getChildAt(i); 112209db0f00a693704eb747027def4279362c51894eChris Banes child.setSelected(i == position); 112309db0f00a693704eb747027def4279362c51894eChris Banes } 1124320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1125320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1126320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1127320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void selectTab(Tab tab) { 112815ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes selectTab(tab, true); 112915ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes } 113015ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes 11316f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes void selectTab(final Tab tab, boolean updateIndicator) { 11326f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes final Tab currentTab = mSelectedTab; 11336f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 11346f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (currentTab == tab) { 11356f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if (currentTab != null) { 11366f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes dispatchTabReselected(tab); 1137320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes animateToTab(tab.getPosition()); 1138320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1139320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1140ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; 114115ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes if (updateIndicator) { 11426f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION) 114315ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes && newPosition != Tab.INVALID_POSITION) { 114415ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes // If we don't currently have a tab, just draw the indicator 114515ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes setScrollPosition(newPosition, 0f, true); 114615ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes } else { 114715ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes animateToTab(newPosition); 114815ed6a51e94cf108c3e5043d13e02c634c44faaeChris Banes } 11491266ef17a626074fbd1cea8afdf0732528a87bb2Chris Banes if (newPosition != Tab.INVALID_POSITION) { 11501266ef17a626074fbd1cea8afdf0732528a87bb2Chris Banes setSelectedTabView(newPosition); 11511266ef17a626074fbd1cea8afdf0732528a87bb2Chris Banes } 1152ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes } 115332ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas if (currentTab != null) { 115432ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas dispatchTabUnselected(currentTab); 115532ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas } 1156320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectedTab = tab; 115732ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas if (tab != null) { 115832ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas dispatchTabSelected(tab); 115932ae746bb6f38afc8f346e993f1e5a816ea8edf8Aurimas Liutikas } 11606f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11616f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11626f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 11636f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private void dispatchTabSelected(@NonNull final Tab tab) { 11646f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { 11656f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListeners.get(i).onTabSelected(tab); 11666f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11676f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11686f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 11696f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private void dispatchTabUnselected(@NonNull final Tab tab) { 11706f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { 11716f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListeners.get(i).onTabUnselected(tab); 11726f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11736f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes } 11746f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes 11756f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes private void dispatchTabReselected(@NonNull final Tab tab) { 11766f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { 11776f9a84a7424c822c628fd0d7ebfaf6abf1656155Chris Banes mSelectedListeners.get(i).onTabReselected(tab); 1178320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1179320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1180320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1181320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int calculateScrollXForTab(int position, float positionOffset) { 1182320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mMode == MODE_SCROLLABLE) { 1183320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final View selectedChild = mTabStrip.getChildAt(position); 1184320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final View nextChild = position + 1 < mTabStrip.getChildCount() 1185320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes ? mTabStrip.getChildAt(position + 1) 1186320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes : null; 1187320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; 1188320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; 1189320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 119086d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren // base scroll amount: places center of tab in center of parent 119186d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (getWidth() / 2); 119286d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren // offset amount: fraction of the distance between centers of tabs 119386d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset); 119486d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren 119586d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) 119686d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren ? scrollBase + scrollOffset 119786d6a88c4d98bf6f125d4a514004bdf86a2a215aJonathan Koren : scrollBase - scrollOffset; 1198320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1199320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return 0; 1200320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1201320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1202320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void applyModeAndGravity() { 1203320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes int paddingStart = 0; 1204320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mMode == MODE_SCROLLABLE) { 1205320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If we're scrollable, or fixed at start, inset using padding 1206320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); 1207320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1208320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); 1209320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1210320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes switch (mMode) { 1211320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes case MODE_FIXED: 1212320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL); 1213320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes break; 1214320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes case MODE_SCROLLABLE: 1215320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabStrip.setGravity(GravityCompat.START); 1216320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes break; 1217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1218320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1219cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes updateTabViews(true); 1220320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1221320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1222657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void updateTabViews(final boolean requestLayout) { 1223320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes for (int i = 0; i < mTabStrip.getChildCount(); i++) { 1224320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes View child = mTabStrip.getChildAt(i); 1225cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes child.setMinimumWidth(getTabMinWidth()); 1226320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); 1227cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (requestLayout) { 1228cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes child.requestLayout(); 1229cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1230320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1231320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1232320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1233320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1234320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * A tab in this layout. Instances can be created via {@link #newTab()}. 1235320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1236320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final class Tab { 1237320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1238320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1239320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * An invalid position for a tab. 1240320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1241320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getPosition() 1242320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1243320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public static final int INVALID_POSITION = -1; 1244320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1245320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private Object mTag; 1246320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private Drawable mIcon; 1247320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private CharSequence mText; 1248320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private CharSequence mContentDesc; 1249320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int mPosition = INVALID_POSITION; 1250320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private View mCustomView; 1251320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1252657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas TabLayout mParent; 1253657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas TabView mView; 1254320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1255657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas Tab() { 125650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes // Private constructor 1257320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1258320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1259320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1260320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return This Tab's tag object. 1261320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 126240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 1263320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public Object getTag() { 1264320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mTag; 1265320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1266320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1267320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1268320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Give this Tab an arbitrary object to hold for later use. 1269320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1270320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param tag Object to store 1271320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1272320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 127340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 127440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setTag(@Nullable Object tag) { 1275320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTag = tag; 1276320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return this; 1277320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1278320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 127921f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes 128021f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes /** 128121f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes * Returns the custom view used for this tab. 128221f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes * 128321f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes * @see #setCustomView(View) 128421f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes * @see #setCustomView(int) 128521f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes */ 128621f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes @Nullable 128721f887ec48f1e9780a69833723e1fba64e7557c5Chris Banes public View getCustomView() { 1288320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mCustomView; 1289320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1290320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1291320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 129240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * Set a custom view to be used for this tab. 129340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * <p> 129440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * If the provided view contains a {@link TextView} with an ID of 129540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * {@link android.R.id#text1} then that will be updated with the value given 129640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * to {@link #setText(CharSequence)}. Similarly, if this layout contains an 129740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with 129840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * the value given to {@link #setIcon(Drawable)}. 129940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * </p> 1300320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1301320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param view Custom view to be used as a tab. 1302320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1303320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 130440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 130540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setCustomView(@Nullable View view) { 1306320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mCustomView = view; 13073a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes updateView(); 1308320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return this; 1309320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1310320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1311320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 131240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * Set a custom view to be used for this tab. 131340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * <p> 131440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * If the inflated layout contains a {@link TextView} with an ID of 131540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * {@link android.R.id#text1} then that will be updated with the value given 131640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * to {@link #setText(CharSequence)}. Similarly, if this layout contains an 131740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with 131840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * the value given to {@link #setIcon(Drawable)}. 131940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes * </p> 1320320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1321b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes * @param resId A layout resource to inflate and use as a custom tab view 1322320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1323320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 132440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 1325b8459a8c30dbaf3c8b82eb3b5d5eaed5526dadedChris Banes public Tab setCustomView(@LayoutRes int resId) { 13263a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes final LayoutInflater inflater = LayoutInflater.from(mView.getContext()); 13273a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes return setCustomView(inflater.inflate(resId, mView, false)); 1328320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1329320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1330320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1331320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Return the icon associated with this tab. 1332320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1333320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The tab's icon 1334320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 133540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 1336320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public Drawable getIcon() { 1337320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mIcon; 1338320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1339320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1340320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1341320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Return the current position of this tab in the action bar. 1342320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1343320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in 1344320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * the action bar. 1345320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1346320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public int getPosition() { 1347320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mPosition; 1348320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1349320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1350320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void setPosition(int position) { 1351320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mPosition = position; 1352320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1353320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1354320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1355320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Return the text of this tab. 1356320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1357320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The tab's text 1358320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 135940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 1360320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public CharSequence getText() { 1361320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mText; 1362320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1363320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1364320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1365320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the icon displayed on this tab. 1366320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1367320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param icon The drawable to use as an icon 1368320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1369320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 137040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 137140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setIcon(@Nullable Drawable icon) { 1372320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIcon = icon; 13733a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes updateView(); 1374320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return this; 1375320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1376320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1377320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1378320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the icon displayed on this tab. 1379320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1380320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param resId A resource ID referring to the icon that should be displayed 1381320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1382320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 138340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 138440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setIcon(@DrawableRes int resId) { 138550f777a07774d0f24529e2888fcba95744cb0be1Chris Banes if (mParent == null) { 138650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes throw new IllegalArgumentException("Tab not attached to a TabLayout"); 138750f777a07774d0f24529e2888fcba95744cb0be1Chris Banes } 13884c99f0e29b0926d8e5de44b7e3980d47f052f04cChris Banes return setIcon(AppCompatResources.getDrawable(mParent.getContext(), resId)); 1389320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1390320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1391320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1392320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the text displayed on this tab. Text may be truncated if there is not room to display 1393320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * the entire string. 1394320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1395320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param text The text to display 1396320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1397320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 139840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 139940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setText(@Nullable CharSequence text) { 1400320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mText = text; 14013a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes updateView(); 1402320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return this; 1403320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1404320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1405320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1406320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set the text displayed on this tab. Text may be truncated if there is not room to display 1407320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * the entire string. 1408320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1409320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param resId A resource ID referring to the text that should be displayed 1410320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1411320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 141240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 141340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setText(@StringRes int resId) { 141450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes if (mParent == null) { 141550f777a07774d0f24529e2888fcba95744cb0be1Chris Banes throw new IllegalArgumentException("Tab not attached to a TabLayout"); 141650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes } 1417320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return setText(mParent.getResources().getText(resId)); 1418320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1419320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1420320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1421320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Select this tab. Only valid if the tab has been added to the action bar. 1422320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 1423320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void select() { 142450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes if (mParent == null) { 142550f777a07774d0f24529e2888fcba95744cb0be1Chris Banes throw new IllegalArgumentException("Tab not attached to a TabLayout"); 142650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes } 1427320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mParent.selectTab(this); 1428320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1429320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1430320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 14311a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes * Returns true if this tab is currently selected. 14321a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes */ 14331a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes public boolean isSelected() { 143450f777a07774d0f24529e2888fcba95744cb0be1Chris Banes if (mParent == null) { 143550f777a07774d0f24529e2888fcba95744cb0be1Chris Banes throw new IllegalArgumentException("Tab not attached to a TabLayout"); 143650f777a07774d0f24529e2888fcba95744cb0be1Chris Banes } 14371a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes return mParent.getSelectedTabPosition() == mPosition; 14381a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes } 14391a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes 14401a2ccecaf4ea84b1a9ecadb0bdba6d99a4935abbChris Banes /** 1441320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set a description of this tab's content for use in accessibility support. If no content 1442320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * description is provided the title will be used. 1443320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1444320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param resId A resource ID referring to the description text 1445320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1446320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setContentDescription(CharSequence) 1447320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getContentDescription() 1448320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 144940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 145040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setContentDescription(@StringRes int resId) { 145150f777a07774d0f24529e2888fcba95744cb0be1Chris Banes if (mParent == null) { 145250f777a07774d0f24529e2888fcba95744cb0be1Chris Banes throw new IllegalArgumentException("Tab not attached to a TabLayout"); 145350f777a07774d0f24529e2888fcba95744cb0be1Chris Banes } 1454320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return setContentDescription(mParent.getResources().getText(resId)); 1455320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1456320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1457320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1458320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Set a description of this tab's content for use in accessibility support. If no content 1459320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * description is provided the title will be used. 1460320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1461320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @param contentDesc Description of this tab's content 1462320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return The current instance for call chaining 1463320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setContentDescription(int) 1464320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #getContentDescription() 1465320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 146640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @NonNull 146740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes public Tab setContentDescription(@Nullable CharSequence contentDesc) { 1468320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mContentDesc = contentDesc; 14693a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes updateView(); 1470320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return this; 1471320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1472320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1473320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes /** 1474320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * Gets a brief description of this tab's content for use in accessibility support. 1475320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * 1476320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @return Description of this tab's content 1477320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setContentDescription(CharSequence) 1478320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes * @see #setContentDescription(int) 1479320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes */ 148040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes @Nullable 1481320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public CharSequence getContentDescription() { 1482320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mContentDesc; 1483320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 148438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 1485657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void updateView() { 14863a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes if (mView != null) { 14873a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes mView.update(); 14883a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } 14893a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } 14903a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes 1491657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void reset() { 149250f777a07774d0f24529e2888fcba95744cb0be1Chris Banes mParent = null; 14933a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes mView = null; 149438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mTag = null; 149538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mIcon = null; 149638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mText = null; 149738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mContentDesc = null; 149838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPosition = INVALID_POSITION; 149938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mCustomView = null; 150038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 1501320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1502320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1503daa85ce5c5aa5d44fabf6675ede7e7be55a79034Vladislav Kaznacheev class TabView extends LinearLayout { 150438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private Tab mTab; 1505320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private TextView mTextView; 1506320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private ImageView mIconView; 150740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes 1508320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private View mCustomView; 150940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes private TextView mCustomTextView; 151040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes private ImageView mCustomIconView; 1511320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1512c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes private int mDefaultMaxLines = 2; 1513c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 151438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes public TabView(Context context) { 1515320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super(context); 1516320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mTabBackgroundResId != 0) { 151747082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas ViewCompat.setBackground( 151847082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas this, AppCompatResources.getDrawable(context, mTabBackgroundResId)); 1519320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1520320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, 1521320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabPaddingEnd, mTabPaddingBottom); 1522320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes setGravity(Gravity.CENTER); 1523c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes setOrientation(VERTICAL); 15243a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes setClickable(true); 1525155d8e15c8f1b959196c16555c54d19a5ccafe99Vladislav Kaznacheev ViewCompat.setPointerIcon(this, 1526155d8e15c8f1b959196c16555c54d19a5ccafe99Vladislav Kaznacheev PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); 15273a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } 15283a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes 15293a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes @Override 15303a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes public boolean performClick() { 1531141f5ae472feb06dc6059f02cab9e88791d30449Alan Viverette final boolean handled = super.performClick(); 15323a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes 15333a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes if (mTab != null) { 1534141f5ae472feb06dc6059f02cab9e88791d30449Alan Viverette if (!handled) { 1535141f5ae472feb06dc6059f02cab9e88791d30449Alan Viverette playSoundEffect(SoundEffectConstants.CLICK); 1536141f5ae472feb06dc6059f02cab9e88791d30449Alan Viverette } 15373a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes mTab.select(); 15383a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes return true; 15393a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } else { 1540141f5ae472feb06dc6059f02cab9e88791d30449Alan Viverette return handled; 15413a1aa2f92d0d07fac2364078b6c603a650ebbe0eChris Banes } 1542320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1543320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1544320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1545ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes public void setSelected(final boolean selected) { 1546ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes final boolean changed = isSelected() != selected; 1547ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes 1548320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.setSelected(selected); 1549ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes 1550ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes if (changed && selected && Build.VERSION.SDK_INT < 16) { 1551ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event 1552320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1553ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes } 1554320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1555ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes // Always dispatch this to the child views, regardless of whether the value has 1556ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes // changed 1557ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes if (mTextView != null) { 1558ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes mTextView.setSelected(selected); 1559ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes } 1560ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes if (mIconView != null) { 1561ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes mIconView.setSelected(selected); 1562ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes } 1563ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes if (mCustomView != null) { 1564ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes mCustomView.setSelected(selected); 1565320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1566320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1567320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1568320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1569320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1570320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.onInitializeAccessibilityEvent(event); 1571320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // This view masquerades as an action bar tab. 1572320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes event.setClassName(ActionBar.Tab.class.getName()); 1573320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1574320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1575320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1576320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1577320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.onInitializeAccessibilityNodeInfo(info); 1578320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // This view masquerades as an action bar tab. 1579320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes info.setClassName(ActionBar.Tab.class.getName()); 1580320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1581320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1582320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 15834172f25e47c484612b50143da44878003db238d1Chris Banes public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) { 15844172f25e47c484612b50143da44878003db238d1Chris Banes final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec); 15854172f25e47c484612b50143da44878003db238d1Chris Banes final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec); 1586cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes final int maxWidth = getTabMaxWidth(); 15874172f25e47c484612b50143da44878003db238d1Chris Banes 15884172f25e47c484612b50143da44878003db238d1Chris Banes final int widthMeasureSpec; 15894172f25e47c484612b50143da44878003db238d1Chris Banes final int heightMeasureSpec = origHeightMeasureSpec; 15904172f25e47c484612b50143da44878003db238d1Chris Banes 15914172f25e47c484612b50143da44878003db238d1Chris Banes if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED 15924172f25e47c484612b50143da44878003db238d1Chris Banes || specWidthSize > maxWidth)) { 15934172f25e47c484612b50143da44878003db238d1Chris Banes // If we have a max width and a given spec which is either unspecified or 15944172f25e47c484612b50143da44878003db238d1Chris Banes // larger than the max width, update the width spec using the same mode 1595c7f2aad754af4373c32fcab613de0ba164ada2a2Kirill Grouchnikov widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST); 1596cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } else { 15974172f25e47c484612b50143da44878003db238d1Chris Banes // Else, use the original width spec 15984172f25e47c484612b50143da44878003db238d1Chris Banes widthMeasureSpec = origWidthMeasureSpec; 1599320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1600c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 16014172f25e47c484612b50143da44878003db238d1Chris Banes // Now lets measure 16024172f25e47c484612b50143da44878003db238d1Chris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 16034172f25e47c484612b50143da44878003db238d1Chris Banes 1604c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // We need to switch the text size based on whether the text is spanning 2 lines or not 1605c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (mTextView != null) { 1606c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final Resources res = getResources(); 1607c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes float textSize = mTabTextSize; 1608c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes int maxLines = mDefaultMaxLines; 1609c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1610c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (mIconView != null && mIconView.getVisibility() == VISIBLE) { 1611c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // If the icon view is being displayed, we limit the text to 1 line 1612c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes maxLines = 1; 1613c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } else if (mTextView != null && mTextView.getLineCount() > 1) { 1614c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // Otherwise when we have text which wraps we reduce the text size 1615c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes textSize = mTabTextMultiLineSize; 1616c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1617c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1618c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final float curTextSize = mTextView.getTextSize(); 1619c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final int curLineCount = mTextView.getLineCount(); 1620092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes final int curMaxLines = TextViewCompat.getMaxLines(mTextView); 1621c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1622092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) { 1623c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // We've got a new text size and/or max lines... 1624c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes boolean updateTextView = true; 1625c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1626c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) { 1627c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // If we're in fixed mode, going up in text size and currently have 1 line 1628c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // then it's very easy to get into an infinite recursion. 1629c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // To combat that we check to see if the change in text size 163027da60cbce61a72f4731cbe67071cbb87136d207Chris Banes // will cause a line count change. If so, abort the size change and stick 163127da60cbce61a72f4731cbe67071cbb87136d207Chris Banes // to the smaller size. 1632c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes final Layout layout = mTextView.getLayout(); 163327da60cbce61a72f4731cbe67071cbb87136d207Chris Banes if (layout == null || approximateLineWidth(layout, 0, textSize) 163427da60cbce61a72f4731cbe67071cbb87136d207Chris Banes > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) { 1635c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes updateTextView = false; 1636c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1637c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1638c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1639c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (updateTextView) { 1640c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 1641c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes mTextView.setMaxLines(maxLines); 1642c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1643c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1644c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1645c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1646320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1647320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1648657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void setTab(@Nullable final Tab tab) { 164938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes if (tab != mTab) { 165038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mTab = tab; 165138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes update(); 165238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 165338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 165438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 1655657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void reset() { 165638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes setTab(null); 165738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes setSelected(false); 165838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 165938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 1660320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final void update() { 1661320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final Tab tab = mTab; 166238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final View custom = tab != null ? tab.getCustomView() : null; 1663320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (custom != null) { 1664320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final ViewParent customParent = custom.getParent(); 1665320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (customParent != this) { 1666320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (customParent != null) { 1667320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes ((ViewGroup) customParent).removeView(custom); 1668320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1669320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes addView(custom); 1670320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1671320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mCustomView = custom; 1672320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mTextView != null) { 1673320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTextView.setVisibility(GONE); 1674320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1675320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mIconView != null) { 1676320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIconView.setVisibility(GONE); 1677320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIconView.setImageDrawable(null); 1678320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 167940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes 168040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mCustomTextView = (TextView) custom.findViewById(android.R.id.text1); 1681c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (mCustomTextView != null) { 1682092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView); 1683c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 168440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon); 1685320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 168640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes // We do not have a custom view. Remove one if it already exists 1687320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mCustomView != null) { 1688320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes removeView(mCustomView); 1689320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mCustomView = null; 1690320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 169140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mCustomTextView = null; 169240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mCustomIconView = null; 169340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 1694320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 169540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (mCustomView == null) { 169640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes // If there isn't a custom view, we'll us our own in-built layouts 169740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (mIconView == null) { 169840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes ImageView iconView = (ImageView) LayoutInflater.from(getContext()) 1699a577676a64e5353b8ec927117151aa6be84adf66Chris Banes .inflate(R.layout.design_layout_tab_icon, this, false); 170040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes addView(iconView, 0); 170140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mIconView = iconView; 1702320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 170340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (mTextView == null) { 170440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes TextView textView = (TextView) LayoutInflater.from(getContext()) 1705a577676a64e5353b8ec927117151aa6be84adf66Chris Banes .inflate(R.layout.design_layout_tab_text, this, false); 170640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes addView(textView); 170740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mTextView = textView; 1708092bd179f5a24c29a63717ce69c6d4065e33abe6Chris Banes mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView); 170940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 17107b75d53b8e5584895595f801256a3d63bb68148aAurimas Liutikas TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance); 171140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (mTabTextColors != null) { 171240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes mTextView.setTextColor(mTabTextColors); 1713320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 171438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes updateTextAndIcon(mTextView, mIconView); 171540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } else { 171640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes // Else, we'll see if there is a TextView or ImageView present and update them 171740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (mCustomTextView != null || mCustomIconView != null) { 171838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes updateTextAndIcon(mCustomTextView, mCustomIconView); 171940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 172040fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 1721ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes 1722ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes // Finally update our selected state 1723ed4b3d49f03292ccc92bef64f34116426aa1096fChris Banes setSelected(tab != null && tab.isSelected()); 172440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 1725320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 172638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private void updateTextAndIcon(@Nullable final TextView textView, 172738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes @Nullable final ImageView iconView) { 172838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final Drawable icon = mTab != null ? mTab.getIcon() : null; 172938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final CharSequence text = mTab != null ? mTab.getText() : null; 173038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null; 173140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes 173240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (iconView != null) { 173340fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (icon != null) { 173440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes iconView.setImageDrawable(icon); 173540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes iconView.setVisibility(VISIBLE); 173640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes setVisibility(VISIBLE); 173740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } else { 173840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes iconView.setVisibility(GONE); 173940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes iconView.setImageDrawable(null); 1740320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 174138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes iconView.setContentDescription(contentDesc); 174240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes } 1743320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 174440fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes final boolean hasText = !TextUtils.isEmpty(text); 174540fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (textView != null) { 174640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes if (hasText) { 174740fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes textView.setText(text); 174840fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes textView.setVisibility(VISIBLE); 174940fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes setVisibility(VISIBLE); 1750320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 175140fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes textView.setVisibility(GONE); 175240fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes textView.setText(null); 1753320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 175438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes textView.setContentDescription(contentDesc); 1755320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 175640fc642141f4ee9ce0763febe67dc258eed04a2bChris Banes 1757c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (iconView != null) { 1758c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams()); 1759c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes int bottomMargin = 0; 1760c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (hasText && iconView.getVisibility() == VISIBLE) { 1761c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes // If we're showing both text and icon, add some margin bottom to the icon 1762c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON); 1763c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1764c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (bottomMargin != lp.bottomMargin) { 1765c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes lp.bottomMargin = bottomMargin; 1766c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes iconView.requestLayout(); 1767c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1768c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1769a4c6825b4b077cb12adb4f14b825001b6fd0e865Vladislav Kaznacheev TooltipCompat.setTooltipText(this, hasText ? null : contentDesc); 1770320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1771320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1772320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes public Tab getTab() { 1773320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return mTab; 1774320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1775c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes 1776c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes /** 1777c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes * Approximates a given lines width with the new provided text size. 1778c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes */ 1779c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes private float approximateLineWidth(Layout layout, int line, float textSize) { 1780c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize()); 1781c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 1782320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1783320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1784320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private class SlidingTabStrip extends LinearLayout { 1785320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int mSelectedIndicatorHeight; 1786320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private final Paint mSelectedIndicatorPaint; 1787320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1788657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int mSelectedPosition = -1; 1789657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas float mSelectionOffset; 1790320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1791618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas private int mLayoutDirection = -1; 1792618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas 1793320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int mIndicatorLeft = -1; 1794320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private int mIndicatorRight = -1; 1795320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1796ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas private ValueAnimator mIndicatorAnimator; 1797ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes 1798320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes SlidingTabStrip(Context context) { 1799320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super(context); 1800320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes setWillNotDraw(false); 1801320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectedIndicatorPaint = new Paint(); 1802320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1803320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1804320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void setSelectedIndicatorColor(int color) { 180509db0f00a693704eb747027def4279362c51894eChris Banes if (mSelectedIndicatorPaint.getColor() != color) { 180609db0f00a693704eb747027def4279362c51894eChris Banes mSelectedIndicatorPaint.setColor(color); 180709db0f00a693704eb747027def4279362c51894eChris Banes ViewCompat.postInvalidateOnAnimation(this); 180809db0f00a693704eb747027def4279362c51894eChris Banes } 1809320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1810320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1811320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void setSelectedIndicatorHeight(int height) { 181209db0f00a693704eb747027def4279362c51894eChris Banes if (mSelectedIndicatorHeight != height) { 181309db0f00a693704eb747027def4279362c51894eChris Banes mSelectedIndicatorHeight = height; 181409db0f00a693704eb747027def4279362c51894eChris Banes ViewCompat.postInvalidateOnAnimation(this); 181509db0f00a693704eb747027def4279362c51894eChris Banes } 1816320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1817320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1818bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes boolean childrenNeedLayout() { 1819bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes for (int i = 0, z = getChildCount(); i < z; i++) { 1820bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes final View child = getChildAt(i); 1821bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes if (child.getWidth() <= 0) { 1822bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes return true; 1823bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes } 1824bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes } 1825bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes return false; 1826bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes } 1827bd085f17cd76603445c1befc020495f7d9dd00d6Chris Banes 1828320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void setIndicatorPositionFromTabPosition(int position, float positionOffset) { 18293fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { 18303fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes mIndicatorAnimator.cancel(); 18313fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes } 18323fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes 1833320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectedPosition = position; 1834320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectionOffset = positionOffset; 1835320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes updateIndicatorPosition(); 1836320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1837320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 183809db0f00a693704eb747027def4279362c51894eChris Banes float getIndicatorPosition() { 183909db0f00a693704eb747027def4279362c51894eChris Banes return mSelectedPosition + mSelectionOffset; 184009db0f00a693704eb747027def4279362c51894eChris Banes } 184109db0f00a693704eb747027def4279362c51894eChris Banes 1842320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1843618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas public void onRtlPropertiesChanged(int layoutDirection) { 1844618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas super.onRtlPropertiesChanged(layoutDirection); 1845618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas 1846618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas // Workaround for a bug before Android M where LinearLayout did not relayout itself when 1847618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas // layout direction changed. 1848618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 1849618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas //noinspection WrongConstant 1850618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas if (mLayoutDirection != layoutDirection) { 1851618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas requestLayout(); 1852618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas mLayoutDirection = layoutDirection; 1853618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas } 1854618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas } 1855618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas } 1856618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas 1857618627e88b517f56e932e1ec44042d21e34c5837Aurimas Liutikas @Override 1858320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 1859320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1860320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1861320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { 1862320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // HorizontalScrollView will first measure use with UNSPECIFIED, and then with 1863320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // EXACTLY. Ignore the first call since anything we do will be overwritten anyway 1864320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return; 1865320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1866320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1867320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { 1868320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int count = getChildCount(); 1869320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1870cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // First we'll find the widest tab 1871320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes int largestTabWidth = 0; 1872320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes for (int i = 0, z = count; i < z; i++) { 1873cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes View child = getChildAt(i); 1874cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (child.getVisibility() == VISIBLE) { 1875cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); 1876cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1877320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1878320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1879320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (largestTabWidth <= 0) { 1880320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If we don't have a largest child yet, skip until the next measure pass 1881320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes return; 1882320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1883320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1884320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); 1885cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes boolean remeasure = false; 1886cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes 1887320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { 1888320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If the tabs fit within our width minus gutters, we will set all tabs to have 1889320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // the same width 1890320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes for (int i = 0; i < count; i++) { 1891cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes final LinearLayout.LayoutParams lp = 1892cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes (LayoutParams) getChildAt(i).getLayoutParams(); 1893cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (lp.width != largestTabWidth || lp.weight != 0) { 1894cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes lp.width = largestTabWidth; 1895cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes lp.weight = 0; 1896cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes remeasure = true; 1897cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1898320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1899320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1900320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If the tabs will wrap to be larger than the width minus gutters, we need 1901320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // to switch to GRAVITY_FILL 1902320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mTabGravity = GRAVITY_FILL; 1903cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes updateTabViews(false); 1904cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes remeasure = true; 1905320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1906320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1907cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (remeasure) { 1908cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // Now re-measure after our changes 1909cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1910cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 1911320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1912320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1913320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1914320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 1915320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes protected void onLayout(boolean changed, int l, int t, int r, int b) { 1916320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes super.onLayout(changed, l, t, r, b); 1917ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes 19183fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { 1919ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes // If we're currently running an animation, lets cancel it and start a 1920ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes // new animation with the remaining duration 19213fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes mIndicatorAnimator.cancel(); 19223fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes final long duration = mIndicatorAnimator.getDuration(); 1923ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes animateIndicatorToPosition(mSelectedPosition, 19243fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); 1925ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes } else { 1926ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes // If we've been layed out, update the indicator position 1927ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes updateIndicatorPosition(); 1928ba2a96aaf54f6fc755856ef05a3c967a5f000b82Chris Banes } 1929320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1930320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1931320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes private void updateIndicatorPosition() { 1932320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final View selectedTitle = getChildAt(mSelectedPosition); 1933320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes int left, right; 1934320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1935320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (selectedTitle != null && selectedTitle.getWidth() > 0) { 1936320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes left = selectedTitle.getLeft(); 1937320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes right = selectedTitle.getRight(); 1938320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1939320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { 1940320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Draw the selection partway between the tabs 1941320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes View nextTitle = getChildAt(mSelectedPosition + 1); 1942320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes left = (int) (mSelectionOffset * nextTitle.getLeft() + 1943320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes (1.0f - mSelectionOffset) * left); 1944320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes right = (int) (mSelectionOffset * nextTitle.getRight() + 1945320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes (1.0f - mSelectionOffset) * right); 1946320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1947320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1948320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes left = right = -1; 1949320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1950320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1951320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes setIndicatorPosition(left, right); 1952320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1953320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1954657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void setIndicatorPosition(int left, int right) { 1955320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (left != mIndicatorLeft || right != mIndicatorRight) { 1956320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If the indicator's left/right has changed, invalidate 1957320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIndicatorLeft = left; 1958320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIndicatorRight = right; 1959320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes ViewCompat.postInvalidateOnAnimation(this); 1960320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1961320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1962320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1963320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes void animateIndicatorToPosition(final int position, int duration) { 19643fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { 19653fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes mIndicatorAnimator.cancel(); 19663fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes } 19673fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes 1968320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final boolean isRtl = ViewCompat.getLayoutDirection(this) 1969320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes == ViewCompat.LAYOUT_DIRECTION_RTL; 1970320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1971320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final View targetView = getChildAt(position); 19729c2232ee41f2db73648348e84cba637b892de617Chris Banes if (targetView == null) { 19739c2232ee41f2db73648348e84cba637b892de617Chris Banes // If we don't have a view, just update the position now and return 19749c2232ee41f2db73648348e84cba637b892de617Chris Banes updateIndicatorPosition(); 19759c2232ee41f2db73648348e84cba637b892de617Chris Banes return; 19769c2232ee41f2db73648348e84cba637b892de617Chris Banes } 19779c2232ee41f2db73648348e84cba637b892de617Chris Banes 1978320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int targetLeft = targetView.getLeft(); 1979320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int targetRight = targetView.getRight(); 1980320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int startLeft; 1981320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int startRight; 1982320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 1983320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (Math.abs(position - mSelectedPosition) <= 1) { 1984320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // If the views are adjacent, we'll animate from edge-to-edge 1985320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startLeft = mIndicatorLeft; 1986320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startRight = mIndicatorRight; 1987320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1988320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Else, we'll just grow from the nearest edge 1989320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); 1990320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (position < mSelectedPosition) { 1991320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // We're going end-to-start 1992320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (isRtl) { 1993320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startLeft = startRight = targetLeft - offset; 1994320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1995320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startLeft = startRight = targetRight + offset; 1996320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 1997320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 1998320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // We're going start-to-end 1999320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (isRtl) { 2000320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startLeft = startRight = targetRight + offset; 2001320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } else { 2002320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes startLeft = startRight = targetLeft - offset; 2003320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2004320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2005320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2006320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 2007320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (startLeft != targetLeft || startRight != targetRight) { 2008ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas ValueAnimator animator = mIndicatorAnimator = new ValueAnimator(); 2009f44e88b81821e61a65886cc7027617ca8995070bChris Banes animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 2010f44e88b81821e61a65886cc7027617ca8995070bChris Banes animator.setDuration(duration); 2011f44e88b81821e61a65886cc7027617ca8995070bChris Banes animator.setFloatValues(0, 1); 2012ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 2013320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 2014ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas public void onAnimationUpdate(ValueAnimator animator) { 2015f44e88b81821e61a65886cc7027617ca8995070bChris Banes final float fraction = animator.getAnimatedFraction(); 2016320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes setIndicatorPosition( 2017f44e88b81821e61a65886cc7027617ca8995070bChris Banes AnimationUtils.lerp(startLeft, targetLeft, fraction), 2018f44e88b81821e61a65886cc7027617ca8995070bChris Banes AnimationUtils.lerp(startRight, targetRight, fraction)); 2019320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2020f44e88b81821e61a65886cc7027617ca8995070bChris Banes }); 2021ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas animator.addListener(new AnimatorListenerAdapter() { 2022320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 2023ae7189188acb2bc972b08d9fbd59939f341c7829Aurimas Liutikas public void onAnimationEnd(Animator animator) { 2024320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectedPosition = position; 2025320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mSelectionOffset = 0f; 2026320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2027320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes }); 2028f44e88b81821e61a65886cc7027617ca8995070bChris Banes animator.start(); 2029320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2030320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2031320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 2032320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes @Override 2033442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes public void draw(Canvas canvas) { 2034442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes super.draw(canvas); 2035442f2c89dbee247e70b6b82af92ce662b3880afdChris Banes 2036320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes // Thick colored underline below the current selection 2037320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) { 2038320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight, 2039320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes mIndicatorRight, getHeight(), mSelectedIndicatorPaint); 2040320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2041320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2042320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes } 2043320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes 204445cbb1d10c865209f373a148556191700d8ea345Chris Banes private static ColorStateList createColorStateList(int defaultColor, int selectedColor) { 204545cbb1d10c865209f373a148556191700d8ea345Chris Banes final int[][] states = new int[2][]; 204645cbb1d10c865209f373a148556191700d8ea345Chris Banes final int[] colors = new int[2]; 204745cbb1d10c865209f373a148556191700d8ea345Chris Banes int i = 0; 204845cbb1d10c865209f373a148556191700d8ea345Chris Banes 204945cbb1d10c865209f373a148556191700d8ea345Chris Banes states[i] = SELECTED_STATE_SET; 205045cbb1d10c865209f373a148556191700d8ea345Chris Banes colors[i] = selectedColor; 205145cbb1d10c865209f373a148556191700d8ea345Chris Banes i++; 205245cbb1d10c865209f373a148556191700d8ea345Chris Banes 205345cbb1d10c865209f373a148556191700d8ea345Chris Banes // Default enabled state 205445cbb1d10c865209f373a148556191700d8ea345Chris Banes states[i] = EMPTY_STATE_SET; 205545cbb1d10c865209f373a148556191700d8ea345Chris Banes colors[i] = defaultColor; 205645cbb1d10c865209f373a148556191700d8ea345Chris Banes i++; 205745cbb1d10c865209f373a148556191700d8ea345Chris Banes 205845cbb1d10c865209f373a148556191700d8ea345Chris Banes return new ColorStateList(states, colors); 205945cbb1d10c865209f373a148556191700d8ea345Chris Banes } 206045cbb1d10c865209f373a148556191700d8ea345Chris Banes 2061c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes private int getDefaultHeight() { 2062c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes boolean hasIconAndText = false; 2063c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes for (int i = 0, count = mTabs.size(); i < count; i++) { 2064c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes Tab tab = mTabs.get(i); 2065c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) { 2066c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes hasIconAndText = true; 2067c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes break; 2068c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes } 206945cbb1d10c865209f373a148556191700d8ea345Chris Banes } 2070c3a5ae275653ae325d0cb5428f4a3aa071aa467bChris Banes return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT; 207145cbb1d10c865209f373a148556191700d8ea345Chris Banes } 207245cbb1d10c865209f373a148556191700d8ea345Chris Banes 2073cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes private int getTabMinWidth() { 2074cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes if (mRequestedTabMinWidth != INVALID_WIDTH) { 2075cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // If we have been given a min width, use it 2076cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes return mRequestedTabMinWidth; 2077cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 2078cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes // Else, we'll use the default value 2079cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0; 2080cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 2081cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes 20824cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes @Override 20834cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes public LayoutParams generateLayoutParams(AttributeSet attrs) { 20844cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes // We don't care about the layout params of any views added to us, since we don't actually 20854cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes // add them. The only view we add is the SlidingTabStrip, which is done manually. 20864cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes // We return the default layout params so that we don't blow up if we're given a TabItem 20874cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes // without android:layout_* values. 20884cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes return generateDefaultLayoutParams(); 20894cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes } 20904cc50ca6952dcc35f1babd918742dbd09e3c01c7Chris Banes 2091657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas int getTabMaxWidth() { 2092cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes return mTabMaxWidth; 2093cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes } 2094cb37b09ebbc5e8e9c7c6c65211686d61e2ed3a3fChris Banes 2095745636602593f0b8ae508eb10266a3c7c675e746Chris Banes /** 2096745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * A {@link ViewPager.OnPageChangeListener} class which contains the 2097745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * necessary calls back to the provided {@link TabLayout} so that the tab position is 2098745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * kept in sync. 2099745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * 2100745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * <p>This class stores the provided TabLayout weakly, meaning that you can use 2101745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) 2102745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and 2103745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * not cause a leak. 2104745636602593f0b8ae508eb10266a3c7c675e746Chris Banes */ 2105745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { 2106745636602593f0b8ae508eb10266a3c7c675e746Chris Banes private final WeakReference<TabLayout> mTabLayoutRef; 210714113e918762c4319dfde99e5b87873deeda9f03Chris Banes private int mPreviousScrollState; 2108745636602593f0b8ae508eb10266a3c7c675e746Chris Banes private int mScrollState; 2109745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2110745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public TabLayoutOnPageChangeListener(TabLayout tabLayout) { 2111745636602593f0b8ae508eb10266a3c7c675e746Chris Banes mTabLayoutRef = new WeakReference<>(tabLayout); 2112745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2113745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2114745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 21153cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes public void onPageScrollStateChanged(final int state) { 211614113e918762c4319dfde99e5b87873deeda9f03Chris Banes mPreviousScrollState = mScrollState; 2117745636602593f0b8ae508eb10266a3c7c675e746Chris Banes mScrollState = state; 2118745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2119745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2120745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 21213cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes public void onPageScrolled(final int position, final float positionOffset, 21223cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes final int positionOffsetPixels) { 2123745636602593f0b8ae508eb10266a3c7c675e746Chris Banes final TabLayout tabLayout = mTabLayoutRef.get(); 2124745636602593f0b8ae508eb10266a3c7c675e746Chris Banes if (tabLayout != null) { 212538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // Only update the text selection if we're not settling, or we are settling after 212638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes // being dragged 212738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || 212838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPreviousScrollState == SCROLL_STATE_DRAGGING; 21293fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // Update the indicator if we're not settling after being idle. This is caused 21303fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // from a setCurrentItem() call and will be handled by an animation from 21313fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes // onPageSelected() instead. 213238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING 213338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes && mPreviousScrollState == SCROLL_STATE_IDLE); 21343fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator); 2135745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2136745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2137745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2138745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 21393cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes public void onPageSelected(final int position) { 214014113e918762c4319dfde99e5b87873deeda9f03Chris Banes final TabLayout tabLayout = mTabLayoutRef.get(); 21413cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes if (tabLayout != null && tabLayout.getSelectedTabPosition() != position 21423cbbf6cc4d8124ae40181b5ce38c7a89e749ff0fChris Banes && position < tabLayout.getTabCount()) { 214314113e918762c4319dfde99e5b87873deeda9f03Chris Banes // Select the tab, only updating the indicator if we're not being dragged/settled 214414113e918762c4319dfde99e5b87873deeda9f03Chris Banes // (since onPageScrolled will handle that). 214538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE 214638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes || (mScrollState == SCROLL_STATE_SETTLING 214738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes && mPreviousScrollState == SCROLL_STATE_IDLE); 21483fbbd54cd1abc2a5471437949f907f2ecf99e946Chris Banes tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); 214914113e918762c4319dfde99e5b87873deeda9f03Chris Banes } 2150745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 215138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 2152657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas void reset() { 215338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE; 215438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 2155745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2156745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2157745636602593f0b8ae508eb10266a3c7c675e746Chris Banes /** 2158745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back 2159745636602593f0b8ae508eb10266a3c7c675e746Chris Banes * to the provided {@link ViewPager} so that the tab position is kept in sync. 2160745636602593f0b8ae508eb10266a3c7c675e746Chris Banes */ 2161745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener { 2162745636602593f0b8ae508eb10266a3c7c675e746Chris Banes private final ViewPager mViewPager; 2163745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2164745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public ViewPagerOnTabSelectedListener(ViewPager viewPager) { 2165745636602593f0b8ae508eb10266a3c7c675e746Chris Banes mViewPager = viewPager; 2166745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2167745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2168745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 2169745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public void onTabSelected(TabLayout.Tab tab) { 2170745636602593f0b8ae508eb10266a3c7c675e746Chris Banes mViewPager.setCurrentItem(tab.getPosition()); 2171745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2172745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2173745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 2174745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public void onTabUnselected(TabLayout.Tab tab) { 2175745636602593f0b8ae508eb10266a3c7c675e746Chris Banes // No-op 2176745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2177745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 2178745636602593f0b8ae508eb10266a3c7c675e746Chris Banes @Override 2179745636602593f0b8ae508eb10266a3c7c675e746Chris Banes public void onTabReselected(TabLayout.Tab tab) { 2180745636602593f0b8ae508eb10266a3c7c675e746Chris Banes // No-op 2181745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2182745636602593f0b8ae508eb10266a3c7c675e746Chris Banes } 2183745636602593f0b8ae508eb10266a3c7c675e746Chris Banes 218438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes private class PagerAdapterObserver extends DataSetObserver { 2185657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas PagerAdapterObserver() { 2186657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas } 2187657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas 218838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes @Override 218938cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes public void onChanged() { 219038cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes populateFromPagerAdapter(); 219138cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 219238cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 219338cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes @Override 219438cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes public void onInvalidated() { 219538cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes populateFromPagerAdapter(); 219638cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 219738cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes } 219838cc44cffde90f6fe689b0df6b82f49481a981f7Chris Banes 21998f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener { 22008f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes private boolean mAutoRefresh; 22018f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 2202657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas AdapterChangeListener() { 2203657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas } 2204657ea1100fee4750f148f9d0dcb7e7e2028f105eAurimas Liutikas 22058f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes @Override 22068f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes public void onAdapterChanged(@NonNull ViewPager viewPager, 22078f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) { 22088f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes if (mViewPager == viewPager) { 22098f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes setPagerAdapter(newAdapter, mAutoRefresh); 22108f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 22118f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 22128f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes 22138f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes void setAutoRefresh(boolean autoRefresh) { 22148f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes mAutoRefresh = autoRefresh; 22158f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 22168f27603fc4caa4931c98a97d4482b1b01bf4bd0fChris Banes } 2217320c3e9fe1e100a5a7cda5dd217d2d1152d08b3fChris Banes} 2218