1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.common.view; 18 19 import android.content.Context; 20 import android.graphics.Typeface; 21 import android.os.Build; 22 import android.support.v4.view.PagerAdapter; 23 import android.support.v4.view.ViewPager; 24 import android.util.AttributeSet; 25 import android.util.TypedValue; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.widget.HorizontalScrollView; 30 import android.widget.TextView; 31 32 /** 33 * To be used with ViewPager to provide a tab indicator component which give constant feedback as to 34 * the user's scroll progress. 35 * <p> 36 * To use the component, simply add it to your view hierarchy. Then in your 37 * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call 38 * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for. 39 * <p> 40 * The colors can be customized in two ways. The first and simplest is to provide an array of colors 41 * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The 42 * alternative is via the {@link TabColorizer} interface which provides you complete control over 43 * which color is used for any individual position. 44 * <p> 45 * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, 46 * providing the layout ID of your custom layout. 47 */ 48 public class SlidingTabLayout extends HorizontalScrollView { 49 50 /** 51 * Allows complete control over the colors drawn in the tab layout. Set with 52 * {@link #setCustomTabColorizer(TabColorizer)}. 53 */ 54 public interface TabColorizer { 55 56 /** 57 * @return return the color of the indicator used when {@code position} is selected. 58 */ getIndicatorColor(int position)59 int getIndicatorColor(int position); 60 61 /** 62 * @return return the color of the divider drawn to the right of {@code position}. 63 */ getDividerColor(int position)64 int getDividerColor(int position); 65 66 } 67 68 private static final int TITLE_OFFSET_DIPS = 24; 69 private static final int TAB_VIEW_PADDING_DIPS = 16; 70 private static final int TAB_VIEW_TEXT_SIZE_SP = 12; 71 72 private int mTitleOffset; 73 74 private int mTabViewLayoutId; 75 private int mTabViewTextViewId; 76 77 private ViewPager mViewPager; 78 private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; 79 80 private final SlidingTabStrip mTabStrip; 81 SlidingTabLayout(Context context)82 public SlidingTabLayout(Context context) { 83 this(context, null); 84 } 85 SlidingTabLayout(Context context, AttributeSet attrs)86 public SlidingTabLayout(Context context, AttributeSet attrs) { 87 this(context, attrs, 0); 88 } 89 SlidingTabLayout(Context context, AttributeSet attrs, int defStyle)90 public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { 91 super(context, attrs, defStyle); 92 93 // Disable the Scroll Bar 94 setHorizontalScrollBarEnabled(false); 95 // Make sure that the Tab Strips fills this View 96 setFillViewport(true); 97 98 mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); 99 100 mTabStrip = new SlidingTabStrip(context); 101 addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 102 } 103 104 /** 105 * Set the custom {@link TabColorizer} to be used. 106 * 107 * If you only require simple custmisation then you can use 108 * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve 109 * similar effects. 110 */ setCustomTabColorizer(TabColorizer tabColorizer)111 public void setCustomTabColorizer(TabColorizer tabColorizer) { 112 mTabStrip.setCustomTabColorizer(tabColorizer); 113 } 114 115 /** 116 * Sets the colors to be used for indicating the selected tab. These colors are treated as a 117 * circular array. Providing one color will mean that all tabs are indicated with the same color. 118 */ setSelectedIndicatorColors(int... colors)119 public void setSelectedIndicatorColors(int... colors) { 120 mTabStrip.setSelectedIndicatorColors(colors); 121 } 122 123 /** 124 * Sets the colors to be used for tab dividers. These colors are treated as a circular array. 125 * Providing one color will mean that all tabs are indicated with the same color. 126 */ setDividerColors(int... colors)127 public void setDividerColors(int... colors) { 128 mTabStrip.setDividerColors(colors); 129 } 130 131 /** 132 * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are 133 * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so 134 * that the layout can update it's scroll position correctly. 135 * 136 * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) 137 */ setOnPageChangeListener(ViewPager.OnPageChangeListener listener)138 public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 139 mViewPagerPageChangeListener = listener; 140 } 141 142 /** 143 * Set the custom layout to be inflated for the tab views. 144 * 145 * @param layoutResId Layout id to be inflated 146 * @param textViewId id of the {@link TextView} in the inflated view 147 */ setCustomTabView(int layoutResId, int textViewId)148 public void setCustomTabView(int layoutResId, int textViewId) { 149 mTabViewLayoutId = layoutResId; 150 mTabViewTextViewId = textViewId; 151 } 152 153 /** 154 * Sets the associated view pager. Note that the assumption here is that the pager content 155 * (number of tabs and tab titles) does not change after this call has been made. 156 */ setViewPager(ViewPager viewPager)157 public void setViewPager(ViewPager viewPager) { 158 mTabStrip.removeAllViews(); 159 160 mViewPager = viewPager; 161 if (viewPager != null) { 162 viewPager.setOnPageChangeListener(new InternalViewPagerListener()); 163 populateTabStrip(); 164 } 165 } 166 167 /** 168 * Create a default view to be used for tabs. This is called if a custom tab view is not set via 169 * {@link #setCustomTabView(int, int)}. 170 */ createDefaultTabView(Context context)171 protected TextView createDefaultTabView(Context context) { 172 TextView textView = new TextView(context); 173 textView.setGravity(Gravity.CENTER); 174 textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); 175 textView.setTypeface(Typeface.DEFAULT_BOLD); 176 177 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 178 // If we're running on Honeycomb or newer, then we can use the Theme's 179 // selectableItemBackground to ensure that the View has a pressed state 180 TypedValue outValue = new TypedValue(); 181 getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, 182 outValue, true); 183 textView.setBackgroundResource(outValue.resourceId); 184 } 185 186 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 187 // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style 188 textView.setAllCaps(true); 189 } 190 191 int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); 192 textView.setPadding(padding, padding, padding, padding); 193 194 return textView; 195 } 196 populateTabStrip()197 private void populateTabStrip() { 198 final PagerAdapter adapter = mViewPager.getAdapter(); 199 final View.OnClickListener tabClickListener = new TabClickListener(); 200 201 for (int i = 0; i < adapter.getCount(); i++) { 202 View tabView = null; 203 TextView tabTitleView = null; 204 205 if (mTabViewLayoutId != 0) { 206 // If there is a custom tab view layout id set, try and inflate it 207 tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, 208 false); 209 tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); 210 } 211 212 if (tabView == null) { 213 tabView = createDefaultTabView(getContext()); 214 } 215 216 if (tabTitleView == null && TextView.class.isInstance(tabView)) { 217 tabTitleView = (TextView) tabView; 218 } 219 220 tabTitleView.setText(adapter.getPageTitle(i)); 221 tabView.setOnClickListener(tabClickListener); 222 223 mTabStrip.addView(tabView); 224 } 225 } 226 227 @Override onAttachedToWindow()228 protected void onAttachedToWindow() { 229 super.onAttachedToWindow(); 230 231 if (mViewPager != null) { 232 scrollToTab(mViewPager.getCurrentItem(), 0); 233 } 234 } 235 scrollToTab(int tabIndex, int positionOffset)236 private void scrollToTab(int tabIndex, int positionOffset) { 237 final int tabStripChildCount = mTabStrip.getChildCount(); 238 if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { 239 return; 240 } 241 242 View selectedChild = mTabStrip.getChildAt(tabIndex); 243 if (selectedChild != null) { 244 int targetScrollX = selectedChild.getLeft() + positionOffset; 245 246 if (tabIndex > 0 || positionOffset > 0) { 247 // If we're not at the first child and are mid-scroll, make sure we obey the offset 248 targetScrollX -= mTitleOffset; 249 } 250 251 scrollTo(targetScrollX, 0); 252 } 253 } 254 255 private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { 256 private int mScrollState; 257 258 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)259 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 260 int tabStripChildCount = mTabStrip.getChildCount(); 261 if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { 262 return; 263 } 264 265 mTabStrip.onViewPagerPageChanged(position, positionOffset); 266 267 View selectedTitle = mTabStrip.getChildAt(position); 268 int extraOffset = (selectedTitle != null) 269 ? (int) (positionOffset * selectedTitle.getWidth()) 270 : 0; 271 scrollToTab(position, extraOffset); 272 273 if (mViewPagerPageChangeListener != null) { 274 mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, 275 positionOffsetPixels); 276 } 277 } 278 279 @Override onPageScrollStateChanged(int state)280 public void onPageScrollStateChanged(int state) { 281 mScrollState = state; 282 283 if (mViewPagerPageChangeListener != null) { 284 mViewPagerPageChangeListener.onPageScrollStateChanged(state); 285 } 286 } 287 288 @Override onPageSelected(int position)289 public void onPageSelected(int position) { 290 if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 291 mTabStrip.onViewPagerPageChanged(position, 0f); 292 scrollToTab(position, 0); 293 } 294 295 if (mViewPagerPageChangeListener != null) { 296 mViewPagerPageChangeListener.onPageSelected(position); 297 } 298 } 299 300 } 301 302 private class TabClickListener implements View.OnClickListener { 303 @Override onClick(View v)304 public void onClick(View v) { 305 for (int i = 0; i < mTabStrip.getChildCount(); i++) { 306 if (v == mTabStrip.getChildAt(i)) { 307 mViewPager.setCurrentItem(i); 308 return; 309 } 310 } 311 } 312 } 313 314 } 315