• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 android.support.design.widget;
18 
19 import android.graphics.Canvas;
20 import android.graphics.ColorFilter;
21 import android.graphics.LinearGradient;
22 import android.graphics.Paint;
23 import android.graphics.PixelFormat;
24 import android.graphics.Rect;
25 import android.graphics.RectF;
26 import android.graphics.Shader;
27 import android.graphics.drawable.Drawable;
28 import android.support.v4.graphics.ColorUtils;
29 
30 /**
31  * A drawable which draws an oval 'border'.
32  */
33 class CircularBorderDrawable extends Drawable {
34 
35     /**
36      * We actually draw the stroke wider than the border size given. This is to reduce any
37      * potential transparent space caused by anti-aliasing and padding rounding.
38      * This value defines the multiplier used to determine to draw stroke width.
39      */
40     private static final float DRAW_STROKE_WIDTH_MULTIPLE = 1.3333f;
41 
42     final Paint mPaint;
43     final Rect mRect = new Rect();
44     final RectF mRectF = new RectF();
45 
46     float mBorderWidth;
47 
48     private int mTopOuterStrokeColor;
49     private int mTopInnerStrokeColor;
50     private int mBottomOuterStrokeColor;
51     private int mBottomInnerStrokeColor;
52 
53     private int mTintColor;
54 
55     private boolean mInvalidateShader = true;
56 
CircularBorderDrawable()57     public CircularBorderDrawable() {
58         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
59         mPaint.setStyle(Paint.Style.STROKE);
60     }
61 
setGradientColors(int topOuterStrokeColor, int topInnerStrokeColor, int bottomOuterStrokeColor, int bottomInnerStrokeColor)62     void setGradientColors(int topOuterStrokeColor, int topInnerStrokeColor,
63             int bottomOuterStrokeColor, int bottomInnerStrokeColor) {
64         mTopOuterStrokeColor = topOuterStrokeColor;
65         mTopInnerStrokeColor = topInnerStrokeColor;
66         mBottomOuterStrokeColor = bottomOuterStrokeColor;
67         mBottomInnerStrokeColor = bottomInnerStrokeColor;
68     }
69 
70     /**
71      * Set the border width
72      */
setBorderWidth(float width)73     void setBorderWidth(float width) {
74         if (mBorderWidth != width) {
75             mBorderWidth = width;
76             mPaint.setStrokeWidth(width * DRAW_STROKE_WIDTH_MULTIPLE);
77             mInvalidateShader = true;
78             invalidateSelf();
79         }
80     }
81 
82     @Override
draw(Canvas canvas)83     public void draw(Canvas canvas) {
84         if (mInvalidateShader) {
85             mPaint.setShader(createGradientShader());
86             mInvalidateShader = false;
87         }
88 
89         final float halfBorderWidth = mPaint.getStrokeWidth() / 2f;
90         final RectF rectF = mRectF;
91 
92         // We need to inset the oval bounds by half the border width. This is because stroke draws
93         // the center of the border on the dimension. Whereas we want the stroke on the inside.
94         copyBounds(mRect);
95         rectF.set(mRect);
96         rectF.left += halfBorderWidth;
97         rectF.top += halfBorderWidth;
98         rectF.right -= halfBorderWidth;
99         rectF.bottom -= halfBorderWidth;
100 
101         // Draw the oval
102         canvas.drawOval(rectF, mPaint);
103     }
104 
105     @Override
getPadding(Rect padding)106     public boolean getPadding(Rect padding) {
107         final int borderWidth = Math.round(mBorderWidth);
108         padding.set(borderWidth, borderWidth, borderWidth, borderWidth);
109         return true;
110     }
111 
112     @Override
setAlpha(int alpha)113     public void setAlpha(int alpha) {
114         mPaint.setAlpha(alpha);
115         invalidateSelf();
116     }
117 
setTintColor(int tintColor)118     void setTintColor(int tintColor) {
119         mTintColor = tintColor;
120         mInvalidateShader = true;
121         invalidateSelf();
122     }
123 
124     @Override
setColorFilter(ColorFilter colorFilter)125     public void setColorFilter(ColorFilter colorFilter) {
126         mPaint.setColorFilter(colorFilter);
127         invalidateSelf();
128     }
129 
130     @Override
getOpacity()131     public int getOpacity() {
132         return mBorderWidth > 0 ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT;
133     }
134 
135     @Override
onBoundsChange(Rect bounds)136     protected void onBoundsChange(Rect bounds) {
137         mInvalidateShader = true;
138     }
139 
140     /**
141      * Creates a vertical {@link LinearGradient}
142      * @return
143      */
createGradientShader()144     private Shader createGradientShader() {
145         final Rect rect = mRect;
146         copyBounds(rect);
147 
148         final float borderRatio = mBorderWidth / rect.height();
149 
150         final int[] colors = new int[6];
151         colors[0] = ColorUtils.compositeColors(mTopOuterStrokeColor, mTintColor);
152         colors[1] = ColorUtils.compositeColors(mTopInnerStrokeColor, mTintColor);
153         colors[2] = ColorUtils.compositeColors(
154                 ColorUtils.setAlphaComponent(mTopInnerStrokeColor, 0), mTintColor);
155         colors[3] = ColorUtils.compositeColors(
156                 ColorUtils.setAlphaComponent(mBottomInnerStrokeColor, 0), mTintColor);
157         colors[4] = ColorUtils.compositeColors(mBottomInnerStrokeColor, mTintColor);
158         colors[5] = ColorUtils.compositeColors(mBottomOuterStrokeColor, mTintColor);
159 
160         final float[] positions = new float[6];
161         positions[0] = 0f;
162         positions[1] = borderRatio;
163         positions[2] = 0.5f;
164         positions[3] = 0.5f;
165         positions[4] = 1f - borderRatio;
166         positions[5] = 1f;
167 
168         return new LinearGradient(
169                 0, rect.top,
170                 0, rect.bottom,
171                 colors, positions,
172                 Shader.TileMode.CLAMP);
173     }
174 }
175