180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes/* 280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * Copyright (C) 2015 The Android Open Source Project 380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * 480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * you may not use this file except in compliance with the License. 680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * You may obtain a copy of the License at 780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * 880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * 1080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * Unless required by applicable law or agreed to in writing, software 1180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 1280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * See the License for the specific language governing permissions and 1480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * limitations under the License. 1580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes */ 1680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 1780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banespackage android.support.design.widget; 1880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 198603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banesimport android.content.res.ColorStateList; 2080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.Canvas; 2180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.ColorFilter; 2280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.LinearGradient; 2380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.Paint; 2480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.PixelFormat; 2580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.Rect; 2680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.RectF; 2780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.Shader; 2880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.graphics.drawable.Drawable; 2980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesimport android.support.v4.graphics.ColorUtils; 3080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 3180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes/** 3280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * A drawable which draws an oval 'border'. 3380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes */ 3480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banesclass CircularBorderDrawable extends Drawable { 3580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 3680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes /** 3780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * We actually draw the stroke wider than the border size given. This is to reduce any 3880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * potential transparent space caused by anti-aliasing and padding rounding. 3980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * This value defines the multiplier used to determine to draw stroke width. 4080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes */ 4180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private static final float DRAW_STROKE_WIDTH_MULTIPLE = 1.3333f; 4280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 4380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final Paint mPaint; 4480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final Rect mRect = new Rect(); 4580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final RectF mRectF = new RectF(); 4680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 4780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes float mBorderWidth; 4880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 4980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private int mTopOuterStrokeColor; 5080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private int mTopInnerStrokeColor; 5180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private int mBottomOuterStrokeColor; 5280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private int mBottomInnerStrokeColor; 5380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 548603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes private ColorStateList mBorderTint; 558603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes private int mCurrentBorderTintColor; 56092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes 5780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private boolean mInvalidateShader = true; 5880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 59d9770e12c8ff2d4417700492c6616572be897e93Chris Banes private float mRotation; 60d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 6180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public CircularBorderDrawable() { 6280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 6380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint.setStyle(Paint.Style.STROKE); 6480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 6580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 6680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes void setGradientColors(int topOuterStrokeColor, int topInnerStrokeColor, 6780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes int bottomOuterStrokeColor, int bottomInnerStrokeColor) { 6880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mTopOuterStrokeColor = topOuterStrokeColor; 6980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mTopInnerStrokeColor = topInnerStrokeColor; 7080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mBottomOuterStrokeColor = bottomOuterStrokeColor; 7180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mBottomInnerStrokeColor = bottomInnerStrokeColor; 7280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 7380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 7480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes /** 7580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * Set the border width 7680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes */ 7780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes void setBorderWidth(float width) { 7880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes if (mBorderWidth != width) { 7980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mBorderWidth = width; 8080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint.setStrokeWidth(width * DRAW_STROKE_WIDTH_MULTIPLE); 8180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mInvalidateShader = true; 8280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes invalidateSelf(); 8380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 8480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 8580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 8680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 8780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public void draw(Canvas canvas) { 8880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes if (mInvalidateShader) { 8980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint.setShader(createGradientShader()); 9080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mInvalidateShader = false; 9180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 9280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 9380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final float halfBorderWidth = mPaint.getStrokeWidth() / 2f; 9480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final RectF rectF = mRectF; 9580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 9680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes // We need to inset the oval bounds by half the border width. This is because stroke draws 9780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes // the center of the border on the dimension. Whereas we want the stroke on the inside. 9880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes copyBounds(mRect); 9980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes rectF.set(mRect); 10080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes rectF.left += halfBorderWidth; 10180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes rectF.top += halfBorderWidth; 10280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes rectF.right -= halfBorderWidth; 10380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes rectF.bottom -= halfBorderWidth; 10480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 105d9770e12c8ff2d4417700492c6616572be897e93Chris Banes canvas.save(); 106d9770e12c8ff2d4417700492c6616572be897e93Chris Banes canvas.rotate(mRotation, rectF.centerX(), rectF.centerY()); 10780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes // Draw the oval 10880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes canvas.drawOval(rectF, mPaint); 109d9770e12c8ff2d4417700492c6616572be897e93Chris Banes canvas.restore(); 11080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 11180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 11280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 11380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public boolean getPadding(Rect padding) { 11480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final int borderWidth = Math.round(mBorderWidth); 11580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes padding.set(borderWidth, borderWidth, borderWidth, borderWidth); 11680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes return true; 11780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 11880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 11980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 12080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public void setAlpha(int alpha) { 12180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint.setAlpha(alpha); 12280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes invalidateSelf(); 12380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 12480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 1258603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes void setBorderTint(ColorStateList tint) { 1268603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes if (tint != null) { 1278603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes mCurrentBorderTintColor = tint.getColorForState(getState(), mCurrentBorderTintColor); 1288603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1298603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes mBorderTint = tint; 130092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes mInvalidateShader = true; 131092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes invalidateSelf(); 132092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes } 133092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes 13480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 13580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public void setColorFilter(ColorFilter colorFilter) { 13680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mPaint.setColorFilter(colorFilter); 13780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes invalidateSelf(); 13880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 13980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 14080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 14180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes public int getOpacity() { 14280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes return mBorderWidth > 0 ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; 14380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 14480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 145d9770e12c8ff2d4417700492c6616572be897e93Chris Banes final void setRotation(float rotation) { 146d9770e12c8ff2d4417700492c6616572be897e93Chris Banes if (rotation != mRotation) { 147d9770e12c8ff2d4417700492c6616572be897e93Chris Banes mRotation = rotation; 148d9770e12c8ff2d4417700492c6616572be897e93Chris Banes invalidateSelf(); 149d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 150d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 151d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 15280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes @Override 15380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes protected void onBoundsChange(Rect bounds) { 15480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes mInvalidateShader = true; 15580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 15680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 1578603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes @Override 1588603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes public boolean isStateful() { 1598603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes return (mBorderTint != null && mBorderTint.isStateful()) || super.isStateful(); 1608603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1618603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes 1628603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes @Override 1638603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes protected boolean onStateChange(int[] state) { 1648603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes if (mBorderTint != null) { 1658603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes final int newColor = mBorderTint.getColorForState(state, mCurrentBorderTintColor); 1668603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes if (newColor != mCurrentBorderTintColor) { 1678603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes mInvalidateShader = true; 1688603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes mCurrentBorderTintColor = newColor; 1698603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1708603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1718603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes if (mInvalidateShader) { 1728603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes invalidateSelf(); 1738603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1748603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes return mInvalidateShader; 1758603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes } 1768603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes 17780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes /** 17880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * Creates a vertical {@link LinearGradient} 17980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes * @return 18080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes */ 18180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes private Shader createGradientShader() { 18280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final Rect rect = mRect; 18380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes copyBounds(rect); 18480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 18580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final float borderRatio = mBorderWidth / rect.height(); 18680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 18780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final int[] colors = new int[6]; 1888603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes colors[0] = ColorUtils.compositeColors(mTopOuterStrokeColor, mCurrentBorderTintColor); 1898603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes colors[1] = ColorUtils.compositeColors(mTopInnerStrokeColor, mCurrentBorderTintColor); 190092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes colors[2] = ColorUtils.compositeColors( 1918603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes ColorUtils.setAlphaComponent(mTopInnerStrokeColor, 0), mCurrentBorderTintColor); 192092f4b4fc5610ed1a2c4f5108066726ee59d8f16Chris Banes colors[3] = ColorUtils.compositeColors( 1938603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes ColorUtils.setAlphaComponent(mBottomInnerStrokeColor, 0), mCurrentBorderTintColor); 1948603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes colors[4] = ColorUtils.compositeColors(mBottomInnerStrokeColor, mCurrentBorderTintColor); 1958603357c9433b9aef1805413a3bfc4a4f8decc50Chris Banes colors[5] = ColorUtils.compositeColors(mBottomOuterStrokeColor, mCurrentBorderTintColor); 19680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 19780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes final float[] positions = new float[6]; 19880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[0] = 0f; 19980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[1] = borderRatio; 20080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[2] = 0.5f; 20180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[3] = 0.5f; 20280de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[4] = 1f - borderRatio; 20380de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes positions[5] = 1f; 20480de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 20580de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes return new LinearGradient( 20680de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 0, rect.top, 20780de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes 0, rect.bottom, 20880de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes colors, positions, 20980de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes Shader.TileMode.CLAMP); 21080de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes } 21180de0674c28a2bd9ade11f24a3b0e46ea83b6847Chris Banes} 212