android.support.design.widget.CircularBorderDrawable.java Source code

Java tutorial

Introduction

Here is the source code for android.support.design.widget.CircularBorderDrawable.java

Source

/*
 * 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);
    }
}