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