Home | History | Annotate | Download | only in view
      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 }