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 57 SlidingTabStrip(Context context) { 58 this(context, null); 59 } 60 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 91 void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { 92 mCustomTabColorizer = customTabColorizer; 93 invalidate(); 94 } 95 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 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 110 void onViewPagerPageChanged(int position, float positionOffset) { 111 mSelectedPosition = position; 112 mSelectionOffset = positionOffset; 113 invalidate(); 114 } 115 116 @Override 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 */ 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 */ 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 191 public final int getIndicatorColor(int position) { 192 return mIndicatorColors[position % mIndicatorColors.length]; 193 } 194 195 @Override 196 public final int getDividerColor(int position) { 197 return mDividerColors[position % mDividerColors.length]; 198 } 199 200 void setIndicatorColors(int... colors) { 201 mIndicatorColors = colors; 202 } 203 204 void setDividerColors(int... colors) { 205 mDividerColors = colors; 206 } 207 } 208 }