public class

CircledImageView

extends View

 java.lang.Object

↳View

↳androidx.wear.widget.CircledImageView

Gradle dependencies

compile group: 'androidx.wear', name: 'wear', version: '1.4.0-alpha01'

  • groupId: androidx.wear
  • artifactId: wear
  • version: 1.4.0-alpha01

Artifact androidx.wear:wear:1.4.0-alpha01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.wear:wear com.android.support:wear

Androidx class mapping:

androidx.wear.widget.CircledImageView android.support.wear.widget.CircledImageView

Overview

An image view surrounded by a circle.

Summary

Constructors
publicCircledImageView(Context context)

publicCircledImageView(Context context, AttributeSet attrs)

publicCircledImageView(Context context, AttributeSet attrs, int defStyle)

Methods
protected voiddrawableStateChanged()

public ColorStateListgetCircleColorStateList()

Gets the circle color.

public floatgetCircleRadius()

Returns the circle radius.

public floatgetCircleRadiusPercent()

Gets the circle radius percent.

public floatgetCircleRadiusPressed()

Gets the circle radius when pressed.

public floatgetCircleRadiusPressedPercent()

Gets the circle radius when pressed as a percent.

public longgetColorChangeAnimationDuration()

public intgetDefaultCircleColor()

Gets the default circle color.

public DrawablegetImageDrawable()

public floatgetInitialCircleRadius()

protected voidonDraw(Canvas canvas)

protected voidonLayout(boolean changed, int left, int top, int right, int bottom)

protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

protected booleanonSetAlpha(int alpha)

public voidonSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)

protected voidonVisibilityChanged(View changedView, int visibility)

protected voidonWindowVisibilityChanged(int visibility)

public voidsetCircleBorderCap(Paint.Cap circleBorderCap)

Set the stroke cap for the border around the circle.

public voidsetCircleBorderColor(int circleBorderColor)

public voidsetCircleBorderWidth(float circleBorderWidth)

Set the border around the circle.

public voidsetCircleColor(int circleColor)

Sets the circle color.

public voidsetCircleColorStateList(ColorStateList circleColor)

Sets the circle color.

public voidsetCircleHidden(boolean circleHidden)

Sets the circle to be hidden.

public voidsetCircleRadius(float circleRadius)

Sets the circle radius.

public voidsetCircleRadiusPercent(float circleRadiusPercent)

Sets the radius of the circle to be a percentage of the largest dimension of the view.

public voidsetCircleRadiusPressed(float circleRadiusPressed)

Sets the circle radius when pressed.

public voidsetCircleRadiusPressedPercent(float circleRadiusPressedPercent)

Sets the radius of the circle to be a percentage of the largest dimension of the view when pressed.

public voidsetColorChangeAnimationDuration(long mColorChangeAnimationDurationMs)

public voidsetImageCirclePercentage(float percentage)

Sets the size of the image based on a percentage in [0, 1].

public voidsetImageDrawable(Drawable drawable)

Sets the image drawable.

public voidsetImageHorizontalOffcenterPercentage(float percentage)

Sets the horizontal offset given a percentage in [0, 1].

public voidsetImageResource(int resId)

Sets the image given a resource.

public voidsetImageTint(int tint)

Sets the tint.

public voidsetPadding(int left, int top, int right, int bottom)

public voidsetPressed(boolean pressed)

public voidsetProgress(float progress)

Sets the progress.

public voidsetShadowVisibility(float shadowVisibility)

Set how much of the shadow should be shown.

public voidshowIndeterminateProgress(boolean show)

Show the circle border as an indeterminate progress spinner.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public CircledImageView(Context context)

public CircledImageView(Context context, AttributeSet attrs)

public CircledImageView(Context context, AttributeSet attrs, int defStyle)

Methods

public void setCircleHidden(boolean circleHidden)

Sets the circle to be hidden.

protected boolean onSetAlpha(int alpha)

protected void onDraw(Canvas canvas)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

protected void onLayout(boolean changed, int left, int top, int right, int bottom)

public void setImageResource(int resId)

Sets the image given a resource.

public void setImageCirclePercentage(float percentage)

Sets the size of the image based on a percentage in [0, 1].

public void setImageHorizontalOffcenterPercentage(float percentage)

Sets the horizontal offset given a percentage in [0, 1].

public void setImageTint(int tint)

Sets the tint.

public float getCircleRadius()

Returns the circle radius.

public void setCircleRadius(float circleRadius)

Sets the circle radius.

public float getCircleRadiusPercent()

Gets the circle radius percent.

public void setCircleRadiusPercent(float circleRadiusPercent)

Sets the radius of the circle to be a percentage of the largest dimension of the view.

Parameters:

circleRadiusPercent: A float from 0 to 1 representing the radius percentage.

public float getCircleRadiusPressed()

Gets the circle radius when pressed.

public void setCircleRadiusPressed(float circleRadiusPressed)

Sets the circle radius when pressed.

public float getCircleRadiusPressedPercent()

Gets the circle radius when pressed as a percent.

public void setCircleRadiusPressedPercent(float circleRadiusPressedPercent)

Sets the radius of the circle to be a percentage of the largest dimension of the view when pressed.

Parameters:

circleRadiusPressedPercent: A float from 0 to 1 representing the radius percentage.

protected void drawableStateChanged()

public void setCircleColor(int circleColor)

Sets the circle color.

public ColorStateList getCircleColorStateList()

Gets the circle color.

public void setCircleColorStateList(ColorStateList circleColor)

Sets the circle color.

public int getDefaultCircleColor()

Gets the default circle color.

public void showIndeterminateProgress(boolean show)

Show the circle border as an indeterminate progress spinner. The views circle border width and color must be set for this to have an effect.

Parameters:

show: true if the progress spinner is shown, false to hide it.

protected void onVisibilityChanged(View changedView, int visibility)

protected void onWindowVisibilityChanged(int visibility)

public void setProgress(float progress)

Sets the progress.

public void setShadowVisibility(float shadowVisibility)

Set how much of the shadow should be shown.

Parameters:

shadowVisibility: Value between 0 and 1.

public float getInitialCircleRadius()

public void setCircleBorderColor(int circleBorderColor)

public void setCircleBorderWidth(float circleBorderWidth)

Set the border around the circle.

Parameters:

circleBorderWidth: Width of the border around the circle.

public void setCircleBorderCap(Paint.Cap circleBorderCap)

Set the stroke cap for the border around the circle.

Parameters:

circleBorderCap: Stroke cap for the border around the circle.

public void setPressed(boolean pressed)

public void setPadding(int left, int top, int right, int bottom)

public void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight)

public Drawable getImageDrawable()

public void setImageDrawable(Drawable drawable)

Sets the image drawable.

public long getColorChangeAnimationDuration()

Returns:

the milliseconds duration of the transition animation when the color changes.

public void setColorChangeAnimationDuration(long mColorChangeAnimationDurationMs)

Parameters:

mColorChangeAnimationDurationMs: the milliseconds duration of the color change animation. The color change animation will run if the color changes with CircledImageView.setCircleColor(int) or as a result of the active state changing.

Source

/*
 * Copyright (C) 2017 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 androidx.wear.widget;

import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.core.view.ViewCompat;
import androidx.wear.R;

import java.util.Objects;

/**
 * An image view surrounded by a circle.
 *
 */
@RestrictTo(Scope.LIBRARY)
public class CircledImageView extends View {

    private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();

    private static final int SQUARE_DIMEN_NONE = 0;
    private static final int SQUARE_DIMEN_HEIGHT = 1;
    private static final int SQUARE_DIMEN_WIDTH = 2;

    private final RectF mOval;
    private final Paint mPaint;
    private final OvalShadowPainter mShadowPainter;
    private final float mInitialCircleRadius;
    private final ProgressDrawable mIndeterminateDrawable;
    private final Rect mIndeterminateBounds = new Rect();
    private final Drawable.Callback mDrawableCallback =
            new Drawable.Callback() {
                @Override
                public void invalidateDrawable(Drawable drawable) {
                    invalidate();
                }

                @Override
                public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
                    // Not needed.
                }

                @Override
                public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
                    // Not needed.
                }
            };
    private ColorStateList mCircleColor;
    private Drawable mDrawable;
    private float mCircleRadius;
    private float mCircleRadiusPercent;
    private float mCircleRadiusPressed;
    private float mCircleRadiusPressedPercent;
    private float mRadiusInset;
    private int mCircleBorderColor;
    private Paint.Cap mCircleBorderCap;
    private float mCircleBorderWidth;
    private boolean mCircleHidden = false;
    private float mProgress = 1f;
    private boolean mPressed = false;
    private boolean mProgressIndeterminate;
    private boolean mVisible;
    private boolean mWindowVisible;
    private long mColorChangeAnimationDurationMs = 0;
    private float mImageCirclePercentage = 1f;
    private float mImageHorizontalOffcenterPercentage = 0f;
    private Integer mImageTint;
    private Integer mSquareDimen;
    int mCurrentColor;

    private final AnimatorUpdateListener mAnimationListener =
            new AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int color = (int) animation.getAnimatedValue();
                    if (color != CircledImageView.this.mCurrentColor) {
                        CircledImageView.this.mCurrentColor = color;
                        CircledImageView.this.invalidate();
                    }
                }
            };

    private ValueAnimator mColorAnimator;

    public CircledImageView(Context context) {
        this(context, null);
    }

    public CircledImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircledImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircledImageView);
        ViewCompat.saveAttributeDataForStyleable(this, context, R.styleable.CircledImageView,
                attrs, a, 0, 0);
        mDrawable = a.getDrawable(R.styleable.CircledImageView_android_src);
        if (mDrawable != null && mDrawable.getConstantState() != null) {
            // The provided Drawable may be used elsewhere, so make a mutable clone before setTint()
            // or setAlpha() is called on it.
            mDrawable =
                    mDrawable.getConstantState()
                            .newDrawable(context.getResources(), context.getTheme());
            mDrawable = mDrawable.mutate();
        }

        mCircleColor = a.getColorStateList(R.styleable.CircledImageView_background_color);
        if (mCircleColor == null) {
            mCircleColor = ColorStateList.valueOf(context.getColor(android.R.color.darker_gray));
        }

        mCircleRadius = a.getDimension(R.styleable.CircledImageView_background_radius, 0);
        mInitialCircleRadius = mCircleRadius;
        mCircleRadiusPressed = a.getDimension(
                R.styleable.CircledImageView_background_radius_pressed, mCircleRadius);
        mCircleBorderColor = a
                .getColor(R.styleable.CircledImageView_background_border_color, Color.BLACK);
        mCircleBorderCap =
                Paint.Cap.values()[a.getInt(R.styleable.CircledImageView_background_border_cap, 0)];
        mCircleBorderWidth = a.getDimension(
                R.styleable.CircledImageView_background_border_width, 0);

        if (mCircleBorderWidth > 0) {
            // The border arc is drawn from the middle of the arc - take that into account.
            mRadiusInset += mCircleBorderWidth / 2;
        }

        float circlePadding = a.getDimension(R.styleable.CircledImageView_img_padding, 0);
        if (circlePadding > 0) {
            mRadiusInset += circlePadding;
        }

        mImageCirclePercentage = a
                .getFloat(R.styleable.CircledImageView_img_circle_percentage, 0f);

        mImageHorizontalOffcenterPercentage =
                a.getFloat(R.styleable.CircledImageView_img_horizontal_offset_percentage, 0f);

        if (a.hasValue(R.styleable.CircledImageView_img_tint)) {
            mImageTint = a.getColor(R.styleable.CircledImageView_img_tint, 0);
        }

        if (a.hasValue(R.styleable.CircledImageView_clip_dimen)) {
            mSquareDimen = a.getInt(R.styleable.CircledImageView_clip_dimen, SQUARE_DIMEN_NONE);
        }

        mCircleRadiusPercent =
                a.getFraction(R.styleable.CircledImageView_background_radius_percent, 1, 1, 0f);

        mCircleRadiusPressedPercent =
                a.getFraction(
                        R.styleable.CircledImageView_background_radius_pressed_percent, 1, 1,
                        mCircleRadiusPercent);

        float shadowWidth = a.getDimension(R.styleable.CircledImageView_background_shadow_width, 0);

        a.recycle();

        mOval = new RectF();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mShadowPainter = new OvalShadowPainter(shadowWidth, 0, getCircleRadius(),
                mCircleBorderWidth);

        mIndeterminateDrawable = new ProgressDrawable();
        // {@link #mDrawableCallback} must be retained as a member, as Drawable callback
        // is held by weak reference, we must retain it for it to continue to be called.
        mIndeterminateDrawable.setCallback(mDrawableCallback);

        setWillNotDraw(false);

        setColorForCurrentState();
    }

    /** Sets the circle to be hidden. */
    public void setCircleHidden(boolean circleHidden) {
        if (circleHidden != mCircleHidden) {
            mCircleHidden = circleHidden;
            invalidate();
        }
    }

    @Override
    protected boolean onSetAlpha(int alpha) {
        return true;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();

        float circleRadius = mPressed ? getCircleRadiusPressed() : getCircleRadius();

        // Maybe draw the shadow
        mShadowPainter.draw(canvas, getAlpha());
        if (mCircleBorderWidth > 0) {
            // First let's find the center of the view.
            mOval.set(
                    paddingLeft,
                    paddingTop,
                    getWidth() - getPaddingRight(),
                    getHeight() - getPaddingBottom());
            // Having the center, lets make the border meet the circle.
            mOval.set(
                    mOval.centerX() - circleRadius,
                    mOval.centerY() - circleRadius,
                    mOval.centerX() + circleRadius,
                    mOval.centerY() + circleRadius);
            mPaint.setColor(mCircleBorderColor);
            // {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
            // color. {@link #Paint.setPaint} will clear any previously set alpha value.
            mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
            mPaint.setStyle(Style.STROKE);
            mPaint.setStrokeWidth(mCircleBorderWidth);
            mPaint.setStrokeCap(mCircleBorderCap);

            if (mProgressIndeterminate) {
                mOval.roundOut(mIndeterminateBounds);
                mIndeterminateDrawable.setBounds(mIndeterminateBounds);
                mIndeterminateDrawable.setRingColor(mCircleBorderColor);
                mIndeterminateDrawable.setRingWidth(mCircleBorderWidth);
                mIndeterminateDrawable.draw(canvas);
            } else {
                canvas.drawArc(mOval, -90, 360 * mProgress, false, mPaint);
            }
        }
        if (!mCircleHidden) {
            mOval.set(
                    paddingLeft,
                    paddingTop,
                    getWidth() - getPaddingRight(),
                    getHeight() - getPaddingBottom());
            // {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
            // color. {@link #Paint.setPaint} will clear any previously set alpha value.
            mPaint.setColor(mCurrentColor);
            mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));

            mPaint.setStyle(Style.FILL);
            float centerX = mOval.centerX();
            float centerY = mOval.centerY();

            canvas.drawCircle(centerX, centerY, circleRadius, mPaint);
        }

        if (mDrawable != null) {
            mDrawable.setAlpha(Math.round(getAlpha() * 255));

            if (mImageTint != null) {
                mDrawable.setTint(mImageTint);
            }
            mDrawable.draw(canvas);
        }

        super.onDraw(canvas);
    }

    private void setColorForCurrentState() {
        int newColor =
                mCircleColor.getColorForState(getDrawableState(), mCircleColor.getDefaultColor());
        if (mColorChangeAnimationDurationMs > 0) {
            if (mColorAnimator != null) {
                mColorAnimator.cancel();
            } else {
                mColorAnimator = new ValueAnimator();
            }
            mColorAnimator.setIntValues(new int[]{mCurrentColor, newColor});
            mColorAnimator.setEvaluator(ARGB_EVALUATOR);
            mColorAnimator.setDuration(mColorChangeAnimationDurationMs);
            mColorAnimator.addUpdateListener(this.mAnimationListener);
            mColorAnimator.start();
        } else {
            if (newColor != mCurrentColor) {
                mCurrentColor = newColor;
                invalidate();
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final float radius =
                getCircleRadius()
                        + mCircleBorderWidth
                        + mShadowPainter.mShadowWidth * mShadowPainter.mShadowVisibility;
        float desiredWidth = radius * 2;
        float desiredHeight = radius * 2;

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = (int) Math.min(desiredWidth, widthSize);
        } else {
            width = (int) desiredWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = (int) Math.min(desiredHeight, heightSize);
        } else {
            height = (int) desiredHeight;
        }

        if (mSquareDimen != null) {
            switch (mSquareDimen) {
                case SQUARE_DIMEN_HEIGHT:
                    width = height;
                    break;
                case SQUARE_DIMEN_WIDTH:
                    height = width;
                    break;
            }
        }

        super.onMeasure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mDrawable != null) {
            // Retrieve the sizes of the drawable and the view.
            final int nativeDrawableWidth = mDrawable.getIntrinsicWidth();
            final int nativeDrawableHeight = mDrawable.getIntrinsicHeight();
            final int viewWidth = getMeasuredWidth();
            final int viewHeight = getMeasuredHeight();
            final float imageCirclePercentage =
                    mImageCirclePercentage > 0 ? mImageCirclePercentage : 1;

            final float scaleFactor =
                    Math.min(
                            1f,
                            Math.min(
                                    (float) nativeDrawableWidth != 0
                                            ? imageCirclePercentage * viewWidth
                                            / nativeDrawableWidth
                                            : 1,
                                    (float) nativeDrawableHeight != 0
                                            ? imageCirclePercentage * viewHeight
                                            / nativeDrawableHeight
                                            : 1));

            // Scale the drawable down to fit the view, if needed.
            final int drawableWidth = Math.round(scaleFactor * nativeDrawableWidth);
            final int drawableHeight = Math.round(scaleFactor * nativeDrawableHeight);

            // Center the drawable within the view.
            final int drawableLeft =
                    (viewWidth - drawableWidth) / 2
                            + Math.round(mImageHorizontalOffcenterPercentage * drawableWidth);
            final int drawableTop = (viewHeight - drawableHeight) / 2;

            mDrawable.setBounds(
                    drawableLeft, drawableTop, drawableLeft + drawableWidth,
                    drawableTop + drawableHeight);
        }

        super.onLayout(changed, left, top, right, bottom);
    }

    /** Sets the image given a resource. */
    public void setImageResource(int resId) {
        setImageDrawable(resId == 0 ? null : getContext().getDrawable(resId));
    }

    /** Sets the size of the image based on a percentage in [0, 1]. */
    public void setImageCirclePercentage(float percentage) {
        float clamped = Math.max(0, Math.min(1, percentage));
        if (clamped != mImageCirclePercentage) {
            mImageCirclePercentage = clamped;
            invalidate();
        }
    }

    /** Sets the horizontal offset given a percentage in [0, 1]. */
    public void setImageHorizontalOffcenterPercentage(float percentage) {
        if (percentage != mImageHorizontalOffcenterPercentage) {
            mImageHorizontalOffcenterPercentage = percentage;
            invalidate();
        }
    }

    /** Sets the tint. */
    public void setImageTint(int tint) {
        if (mImageTint == null || tint != mImageTint) {
            mImageTint = tint;
            invalidate();
        }
    }

    /** Returns the circle radius. */
    public float getCircleRadius() {
        float radius = mCircleRadius;
        if (mCircleRadius <= 0 && mCircleRadiusPercent > 0) {
            radius = Math.max(getMeasuredHeight(), getMeasuredWidth()) * mCircleRadiusPercent;
        }

        return radius - mRadiusInset;
    }

    /** Sets the circle radius. */
    public void setCircleRadius(float circleRadius) {
        if (circleRadius != mCircleRadius) {
            mCircleRadius = circleRadius;
            mShadowPainter
                    .setInnerCircleRadius(mPressed ? getCircleRadiusPressed() : getCircleRadius());
            invalidate();
        }
    }

    /** Gets the circle radius percent. */
    public float getCircleRadiusPercent() {
        return mCircleRadiusPercent;
    }

    /**
     * Sets the radius of the circle to be a percentage of the largest dimension of the view.
     *
     * @param circleRadiusPercent A {@code float} from 0 to 1 representing the radius percentage.
     */
    public void setCircleRadiusPercent(float circleRadiusPercent) {
        if (circleRadiusPercent != mCircleRadiusPercent) {
            mCircleRadiusPercent = circleRadiusPercent;
            mShadowPainter
                    .setInnerCircleRadius(mPressed ? getCircleRadiusPressed() : getCircleRadius());
            invalidate();
        }
    }

    /** Gets the circle radius when pressed. */
    public float getCircleRadiusPressed() {
        float radius = mCircleRadiusPressed;

        if (mCircleRadiusPressed <= 0 && mCircleRadiusPressedPercent > 0) {
            radius =
                    Math.max(getMeasuredHeight(), getMeasuredWidth()) * mCircleRadiusPressedPercent;
        }

        return radius - mRadiusInset;
    }

    /** Sets the circle radius when pressed. */
    public void setCircleRadiusPressed(float circleRadiusPressed) {
        if (circleRadiusPressed != mCircleRadiusPressed) {
            mCircleRadiusPressed = circleRadiusPressed;
            invalidate();
        }
    }

    /** Gets the circle radius when pressed as a percent. */
    public float getCircleRadiusPressedPercent() {
        return mCircleRadiusPressedPercent;
    }

    /**
     * Sets the radius of the circle to be a percentage of the largest dimension of the view when
     * pressed.
     *
     * @param circleRadiusPressedPercent A {@code float} from 0 to 1 representing the radius
     * percentage.
     */
    public void setCircleRadiusPressedPercent(float circleRadiusPressedPercent) {
        if (circleRadiusPressedPercent != mCircleRadiusPressedPercent) {
            mCircleRadiusPressedPercent = circleRadiusPressedPercent;
            mShadowPainter
                    .setInnerCircleRadius(mPressed ? getCircleRadiusPressed() : getCircleRadius());
            invalidate();
        }
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        setColorForCurrentState();
    }

    /** Sets the circle color. */
    public void setCircleColor(int circleColor) {
        setCircleColorStateList(ColorStateList.valueOf(circleColor));
    }

    /** Gets the circle color. */
    public ColorStateList getCircleColorStateList() {
        return mCircleColor;
    }

    /** Sets the circle color. */
    public void setCircleColorStateList(ColorStateList circleColor) {
        if (!Objects.equals(circleColor, mCircleColor)) {
            mCircleColor = circleColor;
            setColorForCurrentState();
            invalidate();
        }
    }

    /** Gets the default circle color. */
    public int getDefaultCircleColor() {
        return mCircleColor.getDefaultColor();
    }

    /**
     * Show the circle border as an indeterminate progress spinner. The views circle border width
     * and color must be set for this to have an effect.
     *
     * @param show true if the progress spinner is shown, false to hide it.
     */
    public void showIndeterminateProgress(boolean show) {
        mProgressIndeterminate = show;
        if (mIndeterminateDrawable != null) {
            if (show && mVisible && mWindowVisible) {
                mIndeterminateDrawable.startAnimation();
            } else {
                mIndeterminateDrawable.stopAnimation();
            }
        }
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        mVisible = (visibility == View.VISIBLE);
        showIndeterminateProgress(mProgressIndeterminate);
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mWindowVisible = (visibility == View.VISIBLE);
        showIndeterminateProgress(mProgressIndeterminate);
    }

    /** Sets the progress. */
    public void setProgress(float progress) {
        if (progress != mProgress) {
            mProgress = progress;
            invalidate();
        }
    }

    /**
     * Set how much of the shadow should be shown.
     *
     * @param shadowVisibility Value between 0 and 1.
     */
    public void setShadowVisibility(float shadowVisibility) {
        if (shadowVisibility != mShadowPainter.mShadowVisibility) {
            mShadowPainter.setShadowVisibility(shadowVisibility);
            invalidate();
        }
    }

    public float getInitialCircleRadius() {
        return mInitialCircleRadius;
    }

    public void setCircleBorderColor(int circleBorderColor) {
        mCircleBorderColor = circleBorderColor;
    }

    /**
     * Set the border around the circle.
     *
     * @param circleBorderWidth Width of the border around the circle.
     */
    public void setCircleBorderWidth(float circleBorderWidth) {
        if (circleBorderWidth != mCircleBorderWidth) {
            mCircleBorderWidth = circleBorderWidth;
            mShadowPainter.setInnerCircleBorderWidth(circleBorderWidth);
            invalidate();
        }
    }

    /**
     * Set the stroke cap for the border around the circle.
     *
     * @param circleBorderCap Stroke cap for the border around the circle.
     */
    public void setCircleBorderCap(Paint.Cap circleBorderCap) {
        if (circleBorderCap != mCircleBorderCap) {
            mCircleBorderCap = circleBorderCap;
            invalidate();
        }
    }

    @Override
    public void setPressed(boolean pressed) {
        super.setPressed(pressed);
        if (pressed != mPressed) {
            mPressed = pressed;
            mShadowPainter
                    .setInnerCircleRadius(mPressed ? getCircleRadiusPressed() : getCircleRadius());
            invalidate();
        }
    }

    @Override
    public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) {
        if (left != getPaddingLeft()
                || top != getPaddingTop()
                || right != getPaddingRight()
                || bottom != getPaddingBottom()) {
            mShadowPainter.setBounds(left, top, getWidth() - right, getHeight() - bottom);
        }
        super.setPadding(left, top, right, bottom);
    }

    @Override
    public void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) {
        if (newWidth != oldWidth || newHeight != oldHeight) {
            mShadowPainter.setBounds(
                    getPaddingLeft(),
                    getPaddingTop(),
                    newWidth - getPaddingRight(),
                    newHeight - getPaddingBottom());
        }
    }

    public Drawable getImageDrawable() {
        return mDrawable;
    }

    /** Sets the image drawable. */
    public void setImageDrawable(Drawable drawable) {
        if (drawable != mDrawable) {
            final Drawable existingDrawable = mDrawable;
            mDrawable = drawable;
            if (mDrawable != null && mDrawable.getConstantState() != null) {
                // The provided Drawable may be used elsewhere, so make a mutable clone before
                // setTint() or setAlpha() is called on it.
                mDrawable =
                        mDrawable
                                .getConstantState()
                                .newDrawable(getResources(), getContext().getTheme())
                                .mutate();
            }

            final boolean skipLayout =
                    drawable != null
                            && existingDrawable != null
                            && existingDrawable.getIntrinsicHeight() == drawable
                            .getIntrinsicHeight()
                            && existingDrawable.getIntrinsicWidth() == drawable.getIntrinsicWidth();

            if (skipLayout) {
                mDrawable.setBounds(existingDrawable.getBounds());
            } else {
                requestLayout();
            }

            invalidate();
        }
    }

    /**
     * @return the milliseconds duration of the transition animation when the color changes.
     */
    public long getColorChangeAnimationDuration() {
        return mColorChangeAnimationDurationMs;
    }

    /**
     * @param mColorChangeAnimationDurationMs the milliseconds duration of the color change
     * animation. The color change animation will run if the color changes with {@link
     * #setCircleColor} or as a result of the active state changing.
     */
    public void setColorChangeAnimationDuration(long mColorChangeAnimationDurationMs) {
        this.mColorChangeAnimationDurationMs = mColorChangeAnimationDurationMs;
    }

    /**
     * Helper class taking care of painting a shadow behind the displayed image. TODO(amad): Replace
     * this with elevation, when moving to support/wearable?
     */
    private static class OvalShadowPainter {

        private final int[] mShaderColors = new int[]{Color.BLACK, Color.TRANSPARENT};
        private final float[] mShaderStops = new float[]{0.6f, 1f};
        private final RectF mBounds = new RectF();
        final float mShadowWidth;
        private final Paint mShadowPaint = new Paint();

        private float mShadowRadius;
        float mShadowVisibility;
        private float mInnerCircleRadius;
        private float mInnerCircleBorderWidth;

        OvalShadowPainter(
                float shadowWidth,
                float shadowVisibility,
                float innerCircleRadius,
                float innerCircleBorderWidth) {
            mShadowWidth = shadowWidth;
            mShadowVisibility = shadowVisibility;
            mInnerCircleRadius = innerCircleRadius;
            mInnerCircleBorderWidth = innerCircleBorderWidth;
            mShadowRadius =
                    mInnerCircleRadius + mInnerCircleBorderWidth + mShadowWidth * mShadowVisibility;
            mShadowPaint.setColor(Color.BLACK);
            mShadowPaint.setStyle(Style.FILL);
            mShadowPaint.setAntiAlias(true);
            updateRadialGradient();
        }

        void draw(Canvas canvas, float alpha) {
            if (mShadowWidth > 0 && mShadowVisibility > 0) {
                mShadowPaint.setAlpha(Math.round(mShadowPaint.getAlpha() * alpha));
                canvas.drawCircle(mBounds.centerX(), mBounds.centerY(), mShadowRadius,
                        mShadowPaint);
            }
        }

        void setBounds(@Px int left, @Px int top, @Px int right, @Px int bottom) {
            mBounds.set(left, top, right, bottom);
            updateRadialGradient();
        }

        void setInnerCircleRadius(float newInnerCircleRadius) {
            mInnerCircleRadius = newInnerCircleRadius;
            updateRadialGradient();
        }

        void setInnerCircleBorderWidth(float newInnerCircleBorderWidth) {
            mInnerCircleBorderWidth = newInnerCircleBorderWidth;
            updateRadialGradient();
        }

        void setShadowVisibility(float newShadowVisibility) {
            mShadowVisibility = newShadowVisibility;
            updateRadialGradient();
        }

        private void updateRadialGradient() {
            // Make the shadow start beyond the circled and possibly the border.
            mShadowRadius =
                    mInnerCircleRadius + mInnerCircleBorderWidth + mShadowWidth * mShadowVisibility;
            // This may happen if the innerCircleRadius has not been correctly computed yet while
            // the view has already been inflated, but not yet measured. In this case, if the view
            // specifies the radius as a percentage of the screen width, then that evaluates to 0
            // and will be corrected after measuring, through onSizeChanged().
            if (mShadowRadius > 0) {
                mShadowPaint.setShader(
                        new RadialGradient(
                                mBounds.centerX(),
                                mBounds.centerY(),
                                mShadowRadius,
                                mShaderColors,
                                mShaderStops,
                                Shader.TileMode.MIRROR));
            }
        }
    }
}