blob: 8e3d5a773b68bb1476c328af6a29d158082fbf1d [file] [log] [blame]
/*
* 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 com.android.car.apps.common;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.animation.DecelerateInterpolator;
/**
* Custom drawable that can be used as the background for fabs.
*
* When not focused or pressed, the fab will be a solid circle of the color specified with
* {@link #setFabColor(int)}. When it is pressed or focused, the fab will grow or shrink
* and it will gain a stroke that has the color specified with {@link #setStrokeColor(int)}.
*
* {@link #FabDrawable(android.content.Context)} provides a quick way to use fab drawable using
* default values for size and animation values provided for consistency.
*
* {@link #FabDrawable(int, int, int)} can also be used for added customization.
* @hide
*/
public class FabDrawable extends Drawable {
private final int mFabGrowth;
private final int mStrokeWidth;
private final Paint mFabPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final ValueAnimator mStrokeAnimator;
private boolean mStrokeAnimatorIsReversing;
private int mFabRadius;
private int mStrokeRadius;
private Outline mOutline;
/**
* Default constructor to provide consistent fab values across uses.
*/
public FabDrawable(Context context) {
this(context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_growth),
context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_stroke_width),
context.getResources().getInteger(R.integer.car_fab_animation_duration));
}
/**
* Custom constructor allows extra customization of the fab's behavior.
*
* @param fabGrowth The amount that the fab should change by when it is focused in pixels.
* @param strokeWidth The width of the stroke when the fab is focused in pixels.
* @param duration The animation duration for the growth of the fab and stroke.
*/
public FabDrawable(int fabGrowth, int strokeWidth, int duration) {
if (fabGrowth < 0) {
throw new IllegalArgumentException("Fab growth must be >= 0.");
} else if (fabGrowth > strokeWidth) {
throw new IllegalArgumentException("Fab growth must be <= strokeWidth.");
} else if (strokeWidth < 0) {
throw new IllegalArgumentException("Stroke width must be >= 0.");
}
mFabGrowth = fabGrowth;
mStrokeWidth = strokeWidth;
mStrokeAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
mStrokeAnimator.setInterpolator(new DecelerateInterpolator());
mStrokeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
updateRadius();
}
});
}
/**
* @param color The primary color of the fab. It will be the entire fab color when not selected
* or pressed and will be the color of the interior circle when selected
* or pressed.
*/
public void setFabColor(int color) {
mFabPaint.setColor(color);
}
/**
* @param color The color of the stroke on the fab that appears when the fab is selected
* or pressed.
*/
public void setStrokeColor(int color) {
mStrokePaint.setColor(color);
}
/**
* Default implementation of {@link #setFabAndStrokeColor(int, float)} with valueMultiplier
* set to 0.9.
*/
public void setFabAndStrokeColor(int color) {
setFabAndStrokeColor(color, 0.9f);
}
/**
* @param color The primary color of the fab.
* @param valueMultiplier The hsv value multiplier that will be set as the stroke color.
*/
public void setFabAndStrokeColor(int color, float valueMultiplier) {
setFabColor(color);
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= valueMultiplier;
setStrokeColor(Color.HSVToColor(hsv));
}
@Override
protected boolean onStateChange(int[] stateSet) {
boolean superChanged = super.onStateChange(stateSet);
boolean focused = false;
boolean pressed = false;
for (int state : stateSet) {
if (state == android.R.attr.state_focused) {
focused = true;
} else if (state == android.R.attr.state_pressed) {
pressed = true;
}
}
if ((focused || pressed) && mStrokeAnimatorIsReversing) {
mStrokeAnimator.start();
mStrokeAnimatorIsReversing = false;
} else if (!(focused || pressed) && !mStrokeAnimatorIsReversing) {
mStrokeAnimator.reverse();
mStrokeAnimatorIsReversing = true;
}
return superChanged || focused;
}
@Override
public void draw(Canvas canvas) {
int cx = canvas.getWidth() / 2;
int cy = canvas.getHeight() / 2;
canvas.drawCircle(cx, cy, mStrokeRadius, mStrokePaint);
canvas.drawCircle(cx, cy, mFabRadius, mFabPaint);
}
@Override
protected void onBoundsChange(Rect bounds) {
updateRadius();
}
@Override
public void setAlpha(int alpha) {
mFabPaint.setAlpha(alpha);
mStrokePaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mFabPaint.setColorFilter(colorFilter);
mStrokePaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void getOutline(Outline outline) {
mOutline = outline;
updateOutline();
}
@Override
public boolean isStateful() {
return true;
}
private void updateRadius() {
int normalRadius = Math.min(getBounds().width(), getBounds().height()) / 2 - mStrokeWidth;
float fraction = mStrokeAnimator.getAnimatedFraction();
mStrokeRadius = (int) (normalRadius + (mStrokeWidth * fraction));
mFabRadius = (int) (normalRadius + (mFabGrowth * fraction));
updateOutline();
invalidateSelf();
}
private void updateOutline() {
int cx = getBounds().width() / 2;
int cy = getBounds().height() / 2;
if (mOutline != null) {
mOutline.setRoundRect(
cx - mStrokeRadius,
cy - mStrokeRadius,
cx + mStrokeRadius,
cy + mStrokeRadius,
mStrokeRadius);
}
}
}