| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.design.widget; |
| |
| import android.content.res.ColorStateList; |
| import android.graphics.Canvas; |
| import android.graphics.ColorFilter; |
| import android.graphics.LinearGradient; |
| import android.graphics.Paint; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.graphics.Shader; |
| import android.graphics.drawable.Drawable; |
| import android.support.v4.graphics.ColorUtils; |
| |
| /** |
| * A drawable which draws an oval 'border'. |
| */ |
| class CircularBorderDrawable extends Drawable { |
| |
| /** |
| * We actually draw the stroke wider than the border size given. This is to reduce any |
| * potential transparent space caused by anti-aliasing and padding rounding. |
| * This value defines the multiplier used to determine to draw stroke width. |
| */ |
| private static final float DRAW_STROKE_WIDTH_MULTIPLE = 1.3333f; |
| |
| final Paint mPaint; |
| final Rect mRect = new Rect(); |
| final RectF mRectF = new RectF(); |
| |
| float mBorderWidth; |
| |
| private int mTopOuterStrokeColor; |
| private int mTopInnerStrokeColor; |
| private int mBottomOuterStrokeColor; |
| private int mBottomInnerStrokeColor; |
| |
| private ColorStateList mBorderTint; |
| private int mCurrentBorderTintColor; |
| |
| private boolean mInvalidateShader = true; |
| |
| private float mRotation; |
| |
| public CircularBorderDrawable() { |
| mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mPaint.setStyle(Paint.Style.STROKE); |
| } |
| |
| void setGradientColors(int topOuterStrokeColor, int topInnerStrokeColor, |
| int bottomOuterStrokeColor, int bottomInnerStrokeColor) { |
| mTopOuterStrokeColor = topOuterStrokeColor; |
| mTopInnerStrokeColor = topInnerStrokeColor; |
| mBottomOuterStrokeColor = bottomOuterStrokeColor; |
| mBottomInnerStrokeColor = bottomInnerStrokeColor; |
| } |
| |
| /** |
| * Set the border width |
| */ |
| void setBorderWidth(float width) { |
| if (mBorderWidth != width) { |
| mBorderWidth = width; |
| mPaint.setStrokeWidth(width * DRAW_STROKE_WIDTH_MULTIPLE); |
| mInvalidateShader = true; |
| invalidateSelf(); |
| } |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| if (mInvalidateShader) { |
| mPaint.setShader(createGradientShader()); |
| mInvalidateShader = false; |
| } |
| |
| final float halfBorderWidth = mPaint.getStrokeWidth() / 2f; |
| final RectF rectF = mRectF; |
| |
| // We need to inset the oval bounds by half the border width. This is because stroke draws |
| // the center of the border on the dimension. Whereas we want the stroke on the inside. |
| copyBounds(mRect); |
| rectF.set(mRect); |
| rectF.left += halfBorderWidth; |
| rectF.top += halfBorderWidth; |
| rectF.right -= halfBorderWidth; |
| rectF.bottom -= halfBorderWidth; |
| |
| canvas.save(); |
| canvas.rotate(mRotation, rectF.centerX(), rectF.centerY()); |
| // Draw the oval |
| canvas.drawOval(rectF, mPaint); |
| canvas.restore(); |
| } |
| |
| @Override |
| public boolean getPadding(Rect padding) { |
| final int borderWidth = Math.round(mBorderWidth); |
| padding.set(borderWidth, borderWidth, borderWidth, borderWidth); |
| return true; |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| mPaint.setAlpha(alpha); |
| invalidateSelf(); |
| } |
| |
| void setBorderTint(ColorStateList tint) { |
| if (tint != null) { |
| mCurrentBorderTintColor = tint.getColorForState(getState(), mCurrentBorderTintColor); |
| } |
| mBorderTint = tint; |
| mInvalidateShader = true; |
| invalidateSelf(); |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { |
| mPaint.setColorFilter(colorFilter); |
| invalidateSelf(); |
| } |
| |
| @Override |
| public int getOpacity() { |
| return mBorderWidth > 0 ? PixelFormat.TRANSLUCENT : PixelFormat.TRANSPARENT; |
| } |
| |
| final void setRotation(float rotation) { |
| if (rotation != mRotation) { |
| mRotation = rotation; |
| invalidateSelf(); |
| } |
| } |
| |
| @Override |
| protected void onBoundsChange(Rect bounds) { |
| mInvalidateShader = true; |
| } |
| |
| @Override |
| public boolean isStateful() { |
| return (mBorderTint != null && mBorderTint.isStateful()) || super.isStateful(); |
| } |
| |
| @Override |
| protected boolean onStateChange(int[] state) { |
| if (mBorderTint != null) { |
| final int newColor = mBorderTint.getColorForState(state, mCurrentBorderTintColor); |
| if (newColor != mCurrentBorderTintColor) { |
| mInvalidateShader = true; |
| mCurrentBorderTintColor = newColor; |
| } |
| } |
| if (mInvalidateShader) { |
| invalidateSelf(); |
| } |
| return mInvalidateShader; |
| } |
| |
| /** |
| * Creates a vertical {@link LinearGradient} |
| * @return |
| */ |
| private Shader createGradientShader() { |
| final Rect rect = mRect; |
| copyBounds(rect); |
| |
| final float borderRatio = mBorderWidth / rect.height(); |
| |
| final int[] colors = new int[6]; |
| colors[0] = ColorUtils.compositeColors(mTopOuterStrokeColor, mCurrentBorderTintColor); |
| colors[1] = ColorUtils.compositeColors(mTopInnerStrokeColor, mCurrentBorderTintColor); |
| colors[2] = ColorUtils.compositeColors( |
| ColorUtils.setAlphaComponent(mTopInnerStrokeColor, 0), mCurrentBorderTintColor); |
| colors[3] = ColorUtils.compositeColors( |
| ColorUtils.setAlphaComponent(mBottomInnerStrokeColor, 0), mCurrentBorderTintColor); |
| colors[4] = ColorUtils.compositeColors(mBottomInnerStrokeColor, mCurrentBorderTintColor); |
| colors[5] = ColorUtils.compositeColors(mBottomOuterStrokeColor, mCurrentBorderTintColor); |
| |
| final float[] positions = new float[6]; |
| positions[0] = 0f; |
| positions[1] = borderRatio; |
| positions[2] = 0.5f; |
| positions[3] = 0.5f; |
| positions[4] = 1f - borderRatio; |
| positions[5] = 1f; |
| |
| return new LinearGradient( |
| 0, rect.top, |
| 0, rect.bottom, |
| colors, positions, |
| Shader.TileMode.CLAMP); |
| } |
| } |