42 | * To use the component, simply add it to your view hierarchy. Then in your 43 | * {@link android.app.Activity} or {@link android.app.Fragment}, {@link 44 | * android.support.v4.app.Fragment} call 45 | * {@link #setViewPager(android.support.v4.view.ViewPager)} providing it the ViewPager this layout 46 | * is being used for. 47 | *
48 | * The colors can be customized in two ways. The first and simplest is to provide an array of 49 | * colors 50 | * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The 51 | * alternative is via the {@link TabColorizer} interface which provides you complete control over 52 | * which color is used for any individual position. 53 | *
54 | * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, 55 | * providing the layout ID of your custom layout. 56 | *
57 | * Forked from Google Samples > SlidingTabsBasic > 58 | * SlidingTabLayout 59 | */ 60 | public class SmartTabLayout extends HorizontalScrollView { 61 | 62 | private static final boolean DEFAULT_DISTRIBUTE_EVENLY = false; 63 | private static final int TITLE_OFFSET_DIPS = 24; 64 | private static final int TAB_VIEW_PADDING_DIPS = 16; 65 | private static final boolean TAB_VIEW_TEXT_ALL_CAPS = true; 66 | private static final int TAB_VIEW_TEXT_SIZE_SP = 12; 67 | private static final int TAB_VIEW_TEXT_COLOR = 0xFC000000; 68 | private static final int TAB_VIEW_TEXT_MIN_WIDTH = 0; 69 | 70 | protected final SmartTabStrip tabStrip; 71 | private int titleOffset; 72 | private int tabViewBackgroundResId; 73 | private boolean tabViewTextAllCaps; 74 | private ColorStateList tabViewTextColors; 75 | private float tabViewTextSize; 76 | private int tabViewTextHorizontalPadding; 77 | private int tabViewTextMinWidth; 78 | private ViewPager viewPager; 79 | private ViewPager.OnPageChangeListener viewPagerPageChangeListener; 80 | private TabProvider tabProvider; 81 | private boolean distributeEvenly; 82 | 83 | public SmartTabLayout(Context context) { 84 | this(context, null); 85 | } 86 | 87 | public SmartTabLayout(Context context, AttributeSet attrs) { 88 | this(context, attrs, 0); 89 | } 90 | 91 | public SmartTabLayout(Context context, AttributeSet attrs, int defStyle) { 92 | super(context, attrs, defStyle); 93 | 94 | // Disable the Scroll Bar 95 | setHorizontalScrollBarEnabled(false); 96 | // Make sure that the Tab Strips fills this View 97 | setFillViewport(true); 98 | 99 | final DisplayMetrics dm = getResources().getDisplayMetrics(); 100 | final float density = dm.density; 101 | 102 | int tabBackgroundResId = NO_ID; 103 | boolean textAllCaps = TAB_VIEW_TEXT_ALL_CAPS; 104 | ColorStateList textColors; 105 | float textSize = TypedValue.applyDimension( 106 | TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP, dm); 107 | int textHorizontalPadding = (int) (TAB_VIEW_PADDING_DIPS * density); 108 | int textMinWidth = (int) (TAB_VIEW_TEXT_MIN_WIDTH * density); 109 | boolean distributeEvenly = DEFAULT_DISTRIBUTE_EVENLY; 110 | int customTabLayoutId = NO_ID; 111 | int customTabTextViewId = NO_ID; 112 | 113 | TypedArray a = context.obtainStyledAttributes( 114 | attrs, R.styleable.stl_SmartTabLayout, defStyle, 0); 115 | tabBackgroundResId = a.getResourceId( 116 | R.styleable.stl_SmartTabLayout_stl_defaultTabBackground, tabBackgroundResId); 117 | textAllCaps = a.getBoolean( 118 | R.styleable.stl_SmartTabLayout_stl_defaultTabTextAllCaps, textAllCaps); 119 | textColors = a.getColorStateList( 120 | R.styleable.stl_SmartTabLayout_stl_defaultTabTextColor); 121 | textSize = a.getDimension( 122 | R.styleable.stl_SmartTabLayout_stl_defaultTabTextSize, textSize); 123 | textHorizontalPadding = a.getDimensionPixelSize( 124 | R.styleable.stl_SmartTabLayout_stl_defaultTabTextHorizontalPadding, textHorizontalPadding); 125 | textMinWidth = a.getDimensionPixelSize( 126 | R.styleable.stl_SmartTabLayout_stl_defaultTabTextMinWidth, textMinWidth); 127 | customTabLayoutId = a.getResourceId( 128 | R.styleable.stl_SmartTabLayout_stl_customTabTextLayoutId, customTabLayoutId); 129 | customTabTextViewId = a.getResourceId( 130 | R.styleable.stl_SmartTabLayout_stl_customTabTextViewId, customTabTextViewId); 131 | distributeEvenly = a.getBoolean( 132 | R.styleable.stl_SmartTabLayout_stl_distributeEvenly, distributeEvenly); 133 | a.recycle(); 134 | 135 | this.titleOffset = (int) (TITLE_OFFSET_DIPS * density); 136 | this.tabViewBackgroundResId = tabBackgroundResId; 137 | this.tabViewTextAllCaps = textAllCaps; 138 | this.tabViewTextColors = (textColors != null) 139 | ? textColors 140 | : ColorStateList.valueOf(TAB_VIEW_TEXT_COLOR); 141 | this.tabViewTextSize = textSize; 142 | this.tabViewTextHorizontalPadding = textHorizontalPadding; 143 | this.tabViewTextMinWidth = textMinWidth; 144 | this.distributeEvenly = distributeEvenly; 145 | 146 | if (customTabLayoutId != NO_ID) { 147 | setCustomTabView(customTabLayoutId, customTabTextViewId); 148 | } 149 | 150 | this.tabStrip = new SmartTabStrip(context, attrs); 151 | 152 | if (distributeEvenly && tabStrip.isIndicatorAlwaysInCenter()) { 153 | throw new UnsupportedOperationException( 154 | "'distributeEvenly' and 'indicatorAlwaysInCenter' both use does not support"); 155 | } 156 | 157 | addView(tabStrip, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 158 | 159 | } 160 | 161 | @Override 162 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 163 | super.onSizeChanged(w, h, oldw, oldh); 164 | if (tabStrip.isIndicatorAlwaysInCenter() && getChildCount() > 0) { 165 | int left = (w - tabStrip.getChildMeasuredWidthAt(0)) / 2; 166 | int right = (w - tabStrip.getChildMeasuredWidthAt(getChildCount() - 1)) / 2; 167 | setPadding(left, getPaddingTop(), right, getPaddingBottom()); 168 | setClipToPadding(false); 169 | } 170 | } 171 | 172 | /** 173 | * Set the behavior of the Indicator scrolling feedback. 174 | * 175 | * @param interpolator {@link com.ogaclejapan.smarttablayout.SmartTabIndicationInterpolator} 176 | */ 177 | public void setIndicationInterpolator(SmartTabIndicationInterpolator interpolator) { 178 | tabStrip.setIndicationInterpolator(interpolator); 179 | } 180 | 181 | /** 182 | * Set the custom {@link TabColorizer} to be used. 183 | * 184 | * If you only require simple customisation then you can use 185 | * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve 186 | * similar effects. 187 | */ 188 | public void setCustomTabColorizer(TabColorizer tabColorizer) { 189 | tabStrip.setCustomTabColorizer(tabColorizer); 190 | } 191 | 192 | /** 193 | * Set the color used for styling the tab text. This will need to be called prior to calling 194 | * {@link #setViewPager(android.support.v4.view.ViewPager)} otherwise it will not get set 195 | * 196 | * @param color to use for tab text 197 | */ 198 | public void setDefaultTabTextColor(int color) { 199 | tabViewTextColors = ColorStateList.valueOf(color); 200 | } 201 | 202 | /** 203 | * Sets the colors used for styling the tab text. This will need to be called prior to calling 204 | * {@link #setViewPager(android.support.v4.view.ViewPager)} otherwise it will not get set 205 | * 206 | * @param colors ColorStateList to use for tab text 207 | */ 208 | public void setDefaultTabTextColor(ColorStateList colors) { 209 | tabViewTextColors = colors; 210 | } 211 | 212 | /** 213 | * Set the same weight for tab 214 | */ 215 | public void setDistributeEvenly(boolean distributeEvenly) { 216 | this.distributeEvenly = distributeEvenly; 217 | } 218 | 219 | /** 220 | * Sets the colors to be used for indicating the selected tab. These colors are treated as a 221 | * circular array. Providing one color will mean that all tabs are indicated with the same color. 222 | */ 223 | public void setSelectedIndicatorColors(int... colors) { 224 | tabStrip.setSelectedIndicatorColors(colors); 225 | } 226 | 227 | /** 228 | * Sets the colors to be used for tab dividers. These colors are treated as a circular array. 229 | * Providing one color will mean that all tabs are indicated with the same color. 230 | */ 231 | public void setDividerColors(int... colors) { 232 | tabStrip.setDividerColors(colors); 233 | } 234 | 235 | /** 236 | * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SmartTabLayout} you are 237 | * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so 238 | * that the layout can update it's scroll position correctly. 239 | * 240 | * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) 241 | */ 242 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 243 | viewPagerPageChangeListener = listener; 244 | } 245 | 246 | /** 247 | * Set the custom layout to be inflated for the tab views. 248 | * 249 | * @param layoutResId Layout id to be inflated 250 | * @param textViewId id of the {@link android.widget.TextView} in the inflated view 251 | */ 252 | public void setCustomTabView(int layoutResId, int textViewId) { 253 | tabProvider = new SimpleTabProvider(getContext(), layoutResId, textViewId); 254 | } 255 | 256 | /** 257 | * Set the custom layout to be inflated for the tab views. 258 | * 259 | * @param provider {@link TabProvider} 260 | */ 261 | public void setCustomTabView(TabProvider provider) { 262 | tabProvider = provider; 263 | } 264 | 265 | /** 266 | * Sets the associated view pager. Note that the assumption here is that the pager content 267 | * (number of tabs and tab titles) does not change after this call has been made. 268 | */ 269 | public void setViewPager(ViewPager viewPager) { 270 | tabStrip.removeAllViews(); 271 | 272 | this.viewPager = viewPager; 273 | if (viewPager != null && viewPager.getAdapter() != null) { 274 | viewPager.setOnPageChangeListener(new InternalViewPagerListener()); 275 | populateTabStrip(); 276 | } 277 | } 278 | 279 | /** 280 | * Returns the view at the specified position in the tabs. 281 | * 282 | * @param position the position at which to get the view from 283 | * @return the view at the specified position or null if the position does not exist within the 284 | * tabs 285 | */ 286 | public View getTabAt(int position) { 287 | return tabStrip.getChildAt(position); 288 | } 289 | 290 | /** 291 | * Create a default view to be used for tabs. This is called if a custom tab view is not set via 292 | * {@link #setCustomTabView(int, int)}. 293 | */ 294 | protected TextView createDefaultTabView(CharSequence title) { 295 | TextView textView = new TextView(getContext()); 296 | textView.setGravity(Gravity.CENTER); 297 | textView.setText(title); 298 | textView.setTextColor(tabViewTextColors); 299 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabViewTextSize); 300 | textView.setTypeface(Typeface.DEFAULT_BOLD); 301 | textView.setLayoutParams(new LinearLayout.LayoutParams( 302 | LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT)); 303 | 304 | if (tabViewBackgroundResId != NO_ID) { 305 | textView.setBackgroundResource(tabViewBackgroundResId); 306 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 307 | // If we're running on Honeycomb or newer, then we can use the Theme's 308 | // selectableItemBackground to ensure that the View has a pressed state 309 | TypedValue outValue = new TypedValue(); 310 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, 311 | outValue, true); 312 | textView.setBackgroundResource(outValue.resourceId); 313 | } 314 | 315 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 316 | // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style 317 | textView.setAllCaps(tabViewTextAllCaps); 318 | } 319 | 320 | textView.setPadding( 321 | tabViewTextHorizontalPadding, 0, 322 | tabViewTextHorizontalPadding, 0); 323 | 324 | if (tabViewTextMinWidth > 0) { 325 | textView.setMinWidth(tabViewTextMinWidth); 326 | } 327 | 328 | return textView; 329 | } 330 | 331 | @Override 332 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 333 | super.onScrollChanged(l, t, oldl, oldt); 334 | } 335 | 336 | private void populateTabStrip() { 337 | final PagerAdapter adapter = viewPager.getAdapter(); 338 | final OnClickListener tabClickListener = new TabClickListener(); 339 | 340 | for (int i = 0; i < adapter.getCount(); i++) { 341 | 342 | final View tabView = (tabProvider == null) 343 | ? createDefaultTabView(adapter.getPageTitle(i)) 344 | : tabProvider.createTabView(tabStrip, i, adapter); 345 | 346 | if (tabView == null) { 347 | throw new IllegalStateException("tabView is null."); 348 | } 349 | 350 | if (distributeEvenly) { 351 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams(); 352 | lp.width = 0; 353 | lp.weight = 1; 354 | } 355 | 356 | tabView.setOnClickListener(tabClickListener); 357 | tabStrip.addView(tabView); 358 | 359 | if (i == viewPager.getCurrentItem()) { 360 | tabView.setSelected(true); 361 | } 362 | 363 | } 364 | } 365 | 366 | @Override 367 | protected void onAttachedToWindow() { 368 | super.onAttachedToWindow(); 369 | 370 | if (viewPager != null) { 371 | scrollToTab(viewPager.getCurrentItem(), 0); 372 | } 373 | } 374 | 375 | private void scrollToTab(int tabIndex, int positionOffset) { 376 | final int tabStripChildCount = tabStrip.getChildCount(); 377 | if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { 378 | return; 379 | } 380 | 381 | View selectedChild = tabStrip.getChildAt(tabIndex); 382 | if (selectedChild != null) { 383 | int targetScrollX = selectedChild.getLeft() + positionOffset; 384 | if (tabStrip.isIndicatorAlwaysInCenter()) { 385 | targetScrollX -= (tabStrip.getChildWidthAt(0) - selectedChild.getWidth()) / 2; 386 | } else if (tabIndex > 0 || positionOffset > 0) { 387 | // If we're not at the first child and are mid-scroll, make sure we obey the offset 388 | targetScrollX -= titleOffset; 389 | } 390 | 391 | scrollTo(targetScrollX, 0); 392 | } 393 | } 394 | 395 | /** 396 | * Allows complete control over the colors drawn in the tab layout. Set with 397 | * {@link #setCustomTabColorizer(TabColorizer)}. 398 | */ 399 | public interface TabColorizer { 400 | 401 | /** 402 | * @return return the color of the indicator used when {@code position} is selected. 403 | */ 404 | int getIndicatorColor(int position); 405 | 406 | /** 407 | * @return return the color of the divider drawn to the right of {@code position}. 408 | */ 409 | int getDividerColor(int position); 410 | 411 | } 412 | 413 | /** 414 | * Create the custom tabs in the tab layout. Set with 415 | * {@link #setCustomTabView(com.ogaclejapan.smarttablayout.SmartTabLayout.TabProvider)} 416 | */ 417 | public interface TabProvider { 418 | 419 | /** 420 | * @return Return the View of {@code position} for the Tabs 421 | */ 422 | View createTabView(ViewGroup container, int position, PagerAdapter adapter); 423 | 424 | } 425 | 426 | private static class SimpleTabProvider implements TabProvider { 427 | 428 | private final LayoutInflater inflater; 429 | private final int tabViewLayoutId; 430 | private final int tabViewTextViewId; 431 | 432 | private SimpleTabProvider(Context context, int layoutResId, int textViewId) { 433 | inflater = LayoutInflater.from(context); 434 | tabViewLayoutId = layoutResId; 435 | tabViewTextViewId = textViewId; 436 | } 437 | 438 | @Override 439 | public View createTabView(ViewGroup container, int position, PagerAdapter adapter) { 440 | View tabView = null; 441 | TextView tabTitleView = null; 442 | 443 | if (tabViewLayoutId != NO_ID) { 444 | tabView = inflater.inflate(tabViewLayoutId, container, false); 445 | } 446 | 447 | if (tabViewTextViewId != NO_ID && tabView != null) { 448 | tabTitleView = (TextView) tabView.findViewById(tabViewTextViewId); 449 | } 450 | 451 | if (tabTitleView == null && TextView.class.isInstance(tabView)) { 452 | tabTitleView = (TextView) tabView; 453 | } 454 | 455 | if (tabTitleView != null) { 456 | tabTitleView.setText(adapter.getPageTitle(position)); 457 | } 458 | 459 | return tabView; 460 | } 461 | 462 | } 463 | 464 | private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { 465 | 466 | private int scrollState; 467 | 468 | @Override 469 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 470 | int tabStripChildCount = tabStrip.getChildCount(); 471 | if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { 472 | return; 473 | } 474 | 475 | tabStrip.onViewPagerPageChanged(position, positionOffset); 476 | 477 | View selectedTitle = tabStrip.getChildAt(position); 478 | int extraOffset = (selectedTitle != null) 479 | ? (int) (positionOffset * selectedTitle.getWidth()) 480 | : 0; 481 | 482 | if (0f < positionOffset && positionOffset < 1f 483 | && tabStrip.isIndicatorAlwaysInCenter()) { 484 | int current = tabStrip.getChildWidthAt(position) / 2; 485 | int next = tabStrip.getChildWidthAt(position + 1) / 2; 486 | extraOffset = Math.round(positionOffset * (current + next)); 487 | } 488 | 489 | scrollToTab(position, extraOffset); 490 | 491 | if (viewPagerPageChangeListener != null) { 492 | viewPagerPageChangeListener.onPageScrolled(position, positionOffset, 493 | positionOffsetPixels); 494 | } 495 | } 496 | 497 | @Override 498 | public void onPageScrollStateChanged(int state) { 499 | scrollState = state; 500 | 501 | if (viewPagerPageChangeListener != null) { 502 | viewPagerPageChangeListener.onPageScrollStateChanged(state); 503 | } 504 | } 505 | 506 | @Override 507 | public void onPageSelected(int position) { 508 | if (scrollState == ViewPager.SCROLL_STATE_IDLE) { 509 | tabStrip.onViewPagerPageChanged(position, 0f); 510 | scrollToTab(position, 0); 511 | } 512 | 513 | for (int i = 0, size = tabStrip.getChildCount(); i < size; i++) { 514 | tabStrip.getChildAt(i).setSelected(position == i); 515 | } 516 | 517 | if (viewPagerPageChangeListener != null) { 518 | viewPagerPageChangeListener.onPageSelected(position); 519 | } 520 | } 521 | 522 | } 523 | 524 | private class TabClickListener implements OnClickListener { 525 | @Override 526 | public void onClick(View v) { 527 | for (int i = 0; i < tabStrip.getChildCount(); i++) { 528 | if (v == tabStrip.getChildAt(i)) { 529 | viewPager.setCurrentItem(i); 530 | return; 531 | } 532 | } 533 | } 534 | } 535 | 536 | } 537 | -------------------------------------------------------------------------------- /library/src/main/java/com/ogaclejapan/smarttablayout/SmartTabStrip.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 ogaclejapan 3 | * Copyright (C) 2013 The Android Open Source Project 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.ogaclejapan.smarttablayout; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.Paint; 24 | import android.graphics.RectF; 25 | import android.util.AttributeSet; 26 | import android.util.TypedValue; 27 | import android.view.View; 28 | import android.widget.LinearLayout; 29 | 30 | /** 31 | *
32 | * Forked from Google Samples > SlidingTabsBasic >
33 | * SlidingTabStrip
34 | */
35 | class SmartTabStrip extends LinearLayout {
36 |
37 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
38 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
39 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
40 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
41 | private static final float DEFAULT_INDICATOR_CORNER_RADIUS = 0f;
42 | private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
43 | private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
44 | private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
45 | private static final boolean DEFAULT_INDICATOR_IN_CENTER = false;
46 | private static final boolean DEFAULT_INDICATOR_IN_FRONT = false;
47 |
48 | private final int bottomBorderThickness;
49 | private final Paint bottomBorderPaint;
50 | private final RectF indicatorRectF = new RectF();
51 | private final boolean indicatorAlwaysInCenter;
52 | private final boolean indicatorInFront;
53 | private final int indicatorThickness;
54 | private final float indicatorCornerRadius;
55 | private final Paint indicatorPaint;
56 | private final Paint dividerPaint;
57 | private final float dividerHeight;
58 | private final SimpleTabColorizer defaultTabColorizer;
59 |
60 | private int lastPosition;
61 | private int selectedPosition;
62 | private float selectionOffset;
63 | private SmartTabIndicationInterpolator indicationInterpolator;
64 | private SmartTabLayout.TabColorizer customTabColorizer;
65 |
66 | SmartTabStrip(Context context, AttributeSet attrs) {
67 | super(context);
68 | setWillNotDraw(false);
69 |
70 | final float density = getResources().getDisplayMetrics().density;
71 |
72 | TypedValue outValue = new TypedValue();
73 | context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
74 | final int themeForegroundColor = outValue.data;
75 |
76 | boolean indicatorInFront = DEFAULT_INDICATOR_IN_FRONT;
77 | boolean indicatorAlwaysInCenter = DEFAULT_INDICATOR_IN_CENTER;
78 | int indicationInterpolatorId = SmartTabIndicationInterpolator.ID_SMART;
79 | int indicatorColor = DEFAULT_SELECTED_INDICATOR_COLOR;
80 | int indicatorColorsId = NO_ID;
81 | int indicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
82 | float indicatorCornerRadius = DEFAULT_INDICATOR_CORNER_RADIUS * density;
83 | int underlineColor = setColorAlpha(themeForegroundColor, DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
84 | int underlineThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
85 | int dividerColor = setColorAlpha(themeForegroundColor, DEFAULT_DIVIDER_COLOR_ALPHA);
86 | int dividerColorsId = NO_ID;
87 | int dividerThickness = (int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density);
88 |
89 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.stl_SmartTabLayout);
90 | indicatorAlwaysInCenter = a.getBoolean(
91 | R.styleable.stl_SmartTabLayout_stl_indicatorAlwaysInCenter, indicatorAlwaysInCenter);
92 | indicatorInFront = a.getBoolean(
93 | R.styleable.stl_SmartTabLayout_stl_indicatorInFront, indicatorInFront);
94 | indicationInterpolatorId = a.getInt(
95 | R.styleable.stl_SmartTabLayout_stl_indicatorInterpolation, indicationInterpolatorId);
96 | indicatorColor = a.getColor(
97 | R.styleable.stl_SmartTabLayout_stl_indicatorColor, indicatorColor);
98 | indicatorColorsId = a.getResourceId(
99 | R.styleable.stl_SmartTabLayout_stl_indicatorColors, indicatorColorsId);
100 | indicatorThickness = a.getDimensionPixelSize(
101 | R.styleable.stl_SmartTabLayout_stl_indicatorThickness, indicatorThickness);
102 | indicatorCornerRadius = a.getDimension(
103 | R.styleable.stl_SmartTabLayout_stl_indicatorCornerRadius, indicatorCornerRadius);
104 | underlineColor = a.getColor(
105 | R.styleable.stl_SmartTabLayout_stl_underlineColor, underlineColor);
106 | underlineThickness = a.getDimensionPixelSize(
107 | R.styleable.stl_SmartTabLayout_stl_underlineThickness, underlineThickness);
108 | dividerColor = a.getColor(
109 | R.styleable.stl_SmartTabLayout_stl_dividerColor, dividerColor);
110 | dividerColorsId = a.getResourceId(
111 | R.styleable.stl_SmartTabLayout_stl_dividerColors, dividerColorsId);
112 | dividerThickness = a.getDimensionPixelSize(
113 | R.styleable.stl_SmartTabLayout_stl_dividerThickness, dividerThickness);
114 | a.recycle();
115 |
116 | final int[] indicatorColors = (indicatorColorsId == NO_ID)
117 | ? new int[] { indicatorColor }
118 | : getResources().getIntArray(indicatorColorsId);
119 |
120 | final int[] dividerColors = (dividerColorsId == NO_ID)
121 | ? new int[] { dividerColor }
122 | : getResources().getIntArray(dividerColorsId);
123 |
124 | this.defaultTabColorizer = new SimpleTabColorizer();
125 | this.defaultTabColorizer.setIndicatorColors(indicatorColors);
126 | this.defaultTabColorizer.setDividerColors(dividerColors);
127 |
128 | this.bottomBorderThickness = underlineThickness;
129 | this.bottomBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
130 | this.bottomBorderPaint.setColor(underlineColor);
131 |
132 | this.indicatorAlwaysInCenter = indicatorAlwaysInCenter;
133 | this.indicatorInFront = indicatorInFront;
134 | this.indicatorThickness = indicatorThickness;
135 | this.indicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
136 | this.indicatorCornerRadius = indicatorCornerRadius;
137 |
138 | this.dividerHeight = DEFAULT_DIVIDER_HEIGHT;
139 | this.dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
140 | this.dividerPaint.setStrokeWidth(dividerThickness);
141 |
142 | this.indicationInterpolator = SmartTabIndicationInterpolator.of(indicationInterpolatorId);
143 | }
144 |
145 | /**
146 | * Set the alpha value of the {@code color} to be the given {@code alpha} value.
147 | */
148 | private static int setColorAlpha(int color, byte alpha) {
149 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
150 | }
151 |
152 | /**
153 | * Blend {@code color1} and {@code color2} using the given ratio.
154 | *
155 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
156 | * 0.0 will return {@code color2}.
157 | */
158 | private static int blendColors(int color1, int color2, float ratio) {
159 | final float inverseRation = 1f - ratio;
160 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
161 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
162 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
163 | return Color.rgb((int) r, (int) g, (int) b);
164 | }
165 |
166 | void setIndicationInterpolator(SmartTabIndicationInterpolator interpolator) {
167 | indicationInterpolator = interpolator;
168 | invalidate();
169 | }
170 |
171 | void setCustomTabColorizer(SmartTabLayout.TabColorizer customTabColorizer) {
172 | this.customTabColorizer = customTabColorizer;
173 | invalidate();
174 | }
175 |
176 | void setSelectedIndicatorColors(int... colors) {
177 | // Make sure that the custom colorizer is removed
178 | customTabColorizer = null;
179 | defaultTabColorizer.setIndicatorColors(colors);
180 | invalidate();
181 | }
182 |
183 | void setDividerColors(int... colors) {
184 | // Make sure that the custom colorizer is removed
185 | customTabColorizer = null;
186 | defaultTabColorizer.setDividerColors(colors);
187 | invalidate();
188 | }
189 |
190 | void onViewPagerPageChanged(int position, float positionOffset) {
191 | selectedPosition = position;
192 | selectionOffset = positionOffset;
193 | if (positionOffset == 0f && lastPosition != selectedPosition) {
194 | lastPosition = selectedPosition;
195 | }
196 | invalidate();
197 | }
198 |
199 | boolean isIndicatorAlwaysInCenter() {
200 | return indicatorAlwaysInCenter;
201 | }
202 |
203 | int getChildMeasuredWidthAt(int index) {
204 | return getChildAt(index).getMeasuredWidth();
205 | }
206 |
207 | int getChildWidthAt(int index) {
208 | return getChildAt(index).getWidth();
209 | }
210 |
211 | @Override
212 | protected void onDraw(Canvas canvas) {
213 | final int height = getHeight();
214 | final int childCount = getChildCount();
215 | final int dividerHeightPx = (int) (Math.min(Math.max(0f, dividerHeight), 1f) * height);
216 | final SmartTabLayout.TabColorizer tabColorizer = (customTabColorizer != null)
217 | ? customTabColorizer
218 | : defaultTabColorizer;
219 |
220 | if (indicatorInFront) {
221 | // Thin underline along the entire bottom edge
222 | canvas.drawRect(0, height - bottomBorderThickness, getWidth(), height,
223 | bottomBorderPaint);
224 | }
225 |
226 | // Thick colored underline below the current selection
227 | if (childCount > 0) {
228 | View selectedTitle = getChildAt(selectedPosition);
229 | int left = selectedTitle.getLeft();
230 | int right = selectedTitle.getRight();
231 | int color = tabColorizer.getIndicatorColor(selectedPosition);
232 | float thickness = indicatorThickness;
233 |
234 | if (selectionOffset > 0f && selectedPosition < (getChildCount() - 1)) {
235 | int nextColor = tabColorizer.getIndicatorColor(selectedPosition + 1);
236 | if (color != nextColor) {
237 | color = blendColors(nextColor, color, selectionOffset);
238 | }
239 |
240 | // Draw the selection partway between the tabs
241 | float leftOffset = indicationInterpolator.getLeftEdge(selectionOffset);
242 | float rightOffset = indicationInterpolator.getRightEdge(selectionOffset);
243 | float thicknessOffset = indicationInterpolator.getThickness(selectionOffset);
244 |
245 | View nextTitle = getChildAt(selectedPosition + 1);
246 | left = (int) (leftOffset * nextTitle.getLeft() +
247 | (1.0f - leftOffset) * left);
248 | right = (int) (rightOffset * nextTitle.getRight() +
249 | (1.0f - rightOffset) * right);
250 | thickness = thickness * thicknessOffset;
251 | }
252 |
253 | indicatorPaint.setColor(color);
254 | indicatorRectF.set(
255 | left, height - (indicatorThickness / 2f) - (thickness / 2f),
256 | right, height - (indicatorThickness / 2f) + (thickness / 2f));
257 |
258 | if (indicatorCornerRadius > 0f) {
259 | canvas.drawRoundRect(
260 | indicatorRectF, indicatorCornerRadius,
261 | indicatorCornerRadius, indicatorPaint);
262 | } else {
263 | canvas.drawRect(indicatorRectF, indicatorPaint);
264 | }
265 | }
266 |
267 | if (!indicatorInFront) {
268 | // Thin underline along the entire bottom edge
269 | canvas.drawRect(0, height - bottomBorderThickness, getWidth(), height,
270 | bottomBorderPaint);
271 | }
272 |
273 | // Vertical separators between the titles
274 | int separatorTop = (height - dividerHeightPx) / 2;
275 | for (int i = 0; i < childCount - 1; i++) {
276 | View child = getChildAt(i);
277 | dividerPaint.setColor(tabColorizer.getDividerColor(i));
278 | canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
279 | separatorTop + dividerHeightPx, dividerPaint);
280 | }
281 | }
282 |
283 | private static class SimpleTabColorizer implements SmartTabLayout.TabColorizer {
284 |
285 | private int[] indicatorColors;
286 | private int[] dividerColors;
287 |
288 | @Override
289 | public final int getIndicatorColor(int position) {
290 | return indicatorColors[position % indicatorColors.length];
291 | }
292 |
293 | @Override
294 | public final int getDividerColor(int position) {
295 | return dividerColors[position % dividerColors.length];
296 | }
297 |
298 | void setIndicatorColors(int... colors) {
299 | indicatorColors = colors;
300 | }
301 |
302 | void setDividerColors(int... colors) {
303 | dividerColors = colors;
304 | }
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 | You should be very careful when using this function. In many
331 | * places where Bundles are used (such as inside of Intent objects), the Bundle
332 | * can live longer inside of another process than the process that had originally
333 | * created it. In that case, the IBinder you supply here will become invalid
334 | * when your process goes away, and no longer usable, even if a new process is
335 | * created for you later on. You should be very careful when using this function. In many
331 | * places where Bundles are used (such as inside of Intent objects), the Bundle
332 | * can live longer inside of another process than the process that had originally
333 | * created it. In that case, the IBinder you supply here will become invalid
334 | * when your process goes away, and no longer usable, even if a new process is
335 | * created for you later on.