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.R;
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Paint;
24 import android.util.AttributeSet;
25 import android.util.TypedValue;
26 import android.view.View;
27 import android.widget.LinearLayout;
28 
29 class SlidingTabStrip extends LinearLayout {
30 
31     private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
32     private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
33     private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
34     private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
35 
36     private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
37     private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
38     private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
39 
40     private final int mBottomBorderThickness;
41     private final Paint mBottomBorderPaint;
42 
43     private final int mSelectedIndicatorThickness;
44     private final Paint mSelectedIndicatorPaint;
45 
46     private final int mDefaultBottomBorderColor;
47 
48     private final Paint mDividerPaint;
49     private final float mDividerHeight;
50 
51     private int mSelectedPosition;
52     private float mSelectionOffset;
53 
54     private SlidingTabLayout.TabColorizer mCustomTabColorizer;
55     private final SimpleTabColorizer mDefaultTabColorizer;
56 
SlidingTabStrip(Context context)57     SlidingTabStrip(Context context) {
58         this(context, null);
59     }
60 
SlidingTabStrip(Context context, AttributeSet attrs)61     SlidingTabStrip(Context context, AttributeSet attrs) {
62         super(context, attrs);
63         setWillNotDraw(false);
64 
65         final float density = getResources().getDisplayMetrics().density;
66 
67         TypedValue outValue = new TypedValue();
68         context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
69         final int themeForegroundColor =  outValue.data;
70 
71         mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
72                 DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
73 
74         mDefaultTabColorizer = new SimpleTabColorizer();
75         mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
76         mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
77                 DEFAULT_DIVIDER_COLOR_ALPHA));
78 
79         mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
80         mBottomBorderPaint = new Paint();
81         mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
82 
83         mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
84         mSelectedIndicatorPaint = new Paint();
85 
86         mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
87         mDividerPaint = new Paint();
88         mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
89     }
90 
setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer)91     void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
92         mCustomTabColorizer = customTabColorizer;
93         invalidate();
94     }
95 
setSelectedIndicatorColors(int... colors)96     void setSelectedIndicatorColors(int... colors) {
97         // Make sure that the custom colorizer is removed
98         mCustomTabColorizer = null;
99         mDefaultTabColorizer.setIndicatorColors(colors);
100         invalidate();
101     }
102 
setDividerColors(int... colors)103     void setDividerColors(int... colors) {
104         // Make sure that the custom colorizer is removed
105         mCustomTabColorizer = null;
106         mDefaultTabColorizer.setDividerColors(colors);
107         invalidate();
108     }
109 
onViewPagerPageChanged(int position, float positionOffset)110     void onViewPagerPageChanged(int position, float positionOffset) {
111         mSelectedPosition = position;
112         mSelectionOffset = positionOffset;
113         invalidate();
114     }
115 
116     @Override
onDraw(Canvas canvas)117     protected void onDraw(Canvas canvas) {
118         final int height = getHeight();
119         final int childCount = getChildCount();
120         final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
121         final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
122                 ? mCustomTabColorizer
123                 : mDefaultTabColorizer;
124 
125         // Thick colored underline below the current selection
126         if (childCount > 0) {
127             View selectedTitle = getChildAt(mSelectedPosition);
128             int left = selectedTitle.getLeft();
129             int right = selectedTitle.getRight();
130             int color = tabColorizer.getIndicatorColor(mSelectedPosition);
131 
132             if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
133                 int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
134                 if (color != nextColor) {
135                     color = blendColors(nextColor, color, mSelectionOffset);
136                 }
137 
138                 // Draw the selection partway between the tabs
139                 View nextTitle = getChildAt(mSelectedPosition + 1);
140                 left = (int) (mSelectionOffset * nextTitle.getLeft() +
141                         (1.0f - mSelectionOffset) * left);
142                 right = (int) (mSelectionOffset * nextTitle.getRight() +
143                         (1.0f - mSelectionOffset) * right);
144             }
145 
146             mSelectedIndicatorPaint.setColor(color);
147 
148             canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
149                     height, mSelectedIndicatorPaint);
150         }
151 
152         // Thin underline along the entire bottom edge
153         canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
154 
155         // Vertical separators between the titles
156         int separatorTop = (height - dividerHeightPx) / 2;
157         for (int i = 0; i < childCount - 1; i++) {
158             View child = getChildAt(i);
159             mDividerPaint.setColor(tabColorizer.getDividerColor(i));
160             canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
161                     separatorTop + dividerHeightPx, mDividerPaint);
162         }
163     }
164 
165     /**
166      * Set the alpha value of the {@code color} to be the given {@code alpha} value.
167      */
setColorAlpha(int color, byte alpha)168     private static int setColorAlpha(int color, byte alpha) {
169         return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
170     }
171 
172     /**
173      * Blend {@code color1} and {@code color2} using the given ratio.
174      *
175      * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
176      *              0.0 will return {@code color2}.
177      */
blendColors(int color1, int color2, float ratio)178     private static int blendColors(int color1, int color2, float ratio) {
179         final float inverseRation = 1f - ratio;
180         float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
181         float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
182         float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
183         return Color.rgb((int) r, (int) g, (int) b);
184     }
185 
186     private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
187         private int[] mIndicatorColors;
188         private int[] mDividerColors;
189 
190         @Override
getIndicatorColor(int position)191         public final int getIndicatorColor(int position) {
192             return mIndicatorColors[position % mIndicatorColors.length];
193         }
194 
195         @Override
getDividerColor(int position)196         public final int getDividerColor(int position) {
197             return mDividerColors[position % mDividerColors.length];
198         }
199 
setIndicatorColors(int... colors)200         void setIndicatorColors(int... colors) {
201             mIndicatorColors = colors;
202         }
203 
setDividerColors(int... colors)204         void setDividerColors(int... colors) {
205             mDividerColors = colors;
206         }
207     }
208 }