summaryrefslogtreecommitdiffstats
path: root/src/android/support/wearable/view/CircledImageView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/support/wearable/view/CircledImageView.java')
-rw-r--r--src/android/support/wearable/view/CircledImageView.java603
1 files changed, 603 insertions, 0 deletions
diff --git a/src/android/support/wearable/view/CircledImageView.java b/src/android/support/wearable/view/CircledImageView.java
new file mode 100644
index 00000000..53cb78cf
--- /dev/null
+++ b/src/android/support/wearable/view/CircledImageView.java
@@ -0,0 +1,603 @@
+/*
+ * 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.wearable.view;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
+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.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Objects;
+import com.android.packageinstaller.R;
+
+import com.android.packageinstaller.R;
+
+/**
+ * An image view surrounded by a circle.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class CircledImageView extends View {
+
+ private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
+
+ private Drawable mDrawable;
+
+ private final RectF mOval;
+ private final Paint mPaint;
+
+ private ColorStateList mCircleColor;
+
+ private float mCircleRadius;
+ private float mCircleRadiusPercent;
+
+ private float mCircleRadiusPressed;
+ private float mCircleRadiusPressedPercent;
+
+ private float mRadiusInset;
+
+ private int mCircleBorderColor;
+
+ private float mCircleBorderWidth;
+ private float mProgress = 1f;
+ private final float mShadowWidth;
+
+ private float mShadowVisibility;
+ private boolean mCircleHidden = false;
+
+ private float mInitialCircleRadius;
+
+ private boolean mPressed = false;
+
+ private boolean mProgressIndeterminate;
+ private ProgressDrawable mIndeterminateDrawable;
+ private Rect mIndeterminateBounds = new Rect();
+ private long mColorChangeAnimationDurationMs = 0;
+
+ private float mImageCirclePercentage = 1f;
+ private float mImageHorizontalOffcenterPercentage = 0f;
+ private Integer mImageTint;
+
+ 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 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);
+ mDrawable = a.getDrawable(R.styleable.CircledImageView_android_src);
+
+ mCircleColor = a.getColorStateList(R.styleable.CircledImageView_circle_color);
+ if (mCircleColor == null) {
+ mCircleColor = ColorStateList.valueOf(android.R.color.darker_gray);
+ }
+
+ mCircleRadius = a.getDimension(
+ R.styleable.CircledImageView_circle_radius, 0);
+ mInitialCircleRadius = mCircleRadius;
+ mCircleRadiusPressed = a.getDimension(
+ R.styleable.CircledImageView_circle_radius_pressed, mCircleRadius);
+ mCircleBorderColor = a.getColor(
+ R.styleable.CircledImageView_circle_border_color, Color.BLACK);
+ mCircleBorderWidth = a.getDimension(R.styleable.CircledImageView_circle_border_width, 0);
+
+ if (mCircleBorderWidth > 0) {
+ mRadiusInset += mCircleBorderWidth;
+ }
+
+ float circlePadding = a.getDimension(R.styleable.CircledImageView_circle_padding, 0);
+ if (circlePadding > 0) {
+ mRadiusInset += circlePadding;
+ }
+ mShadowWidth = a.getDimension(R.styleable.CircledImageView_shadow_width, 0);
+
+ mImageCirclePercentage = a.getFloat(
+ R.styleable.CircledImageView_image_circle_percentage, 0f);
+
+ mImageHorizontalOffcenterPercentage = a.getFloat(
+ R.styleable.CircledImageView_image_horizontal_offcenter_percentage, 0f);
+
+ if (a.hasValue(R.styleable.CircledImageView_image_tint)) {
+ mImageTint = a.getColor(R.styleable.CircledImageView_image_tint, 0);
+ }
+
+ mCircleRadiusPercent = a.getFraction(R.styleable.CircledImageView_circle_radius_percent,
+ 1, 1, 0f);
+
+ mCircleRadiusPressedPercent = a.getFraction(
+ R.styleable.CircledImageView_circle_radius_pressed_percent, 1, 1,
+ mCircleRadiusPercent);
+
+ a.recycle();
+
+ mOval = new RectF();
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+
+ 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();
+ }
+
+ public void setCircleHidden(boolean circleHidden) {
+ if (circleHidden != mCircleHidden) {
+ mCircleHidden = circleHidden;
+ invalidate();
+ }
+ }
+
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+
+
+ float circleRadius = mPressed ? getCircleRadiusPressed() : getCircleRadius();
+ if (mShadowWidth > 0 && mShadowVisibility > 0) {
+ // First let's find the center of the view.
+ mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
+ getHeight() - getPaddingBottom());
+ // Having the center, lets make the shadow start beyond the circled and possibly the
+ // border.
+ final float radius = circleRadius + mCircleBorderWidth +
+ mShadowWidth * mShadowVisibility;
+ mPaint.setColor(Color.BLACK);
+ mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
+ mPaint.setStyle(Style.FILL);
+ // TODO: precalc and pre-allocate this
+ mPaint.setShader(new RadialGradient(mOval.centerX(), mOval.centerY(), radius,
+ new int[]{Color.BLACK, Color.TRANSPARENT}, new float[]{0.6f, 1f},
+ Shader.TileMode.MIRROR));
+ canvas.drawCircle(mOval.centerX(), mOval.centerY(), radius, mPaint);
+ mPaint.setShader(null);
+ }
+ 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);
+
+ 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 +
+ mShadowWidth * 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;
+ }
+
+ 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);
+ }
+
+ public void setImageDrawable(Drawable drawable) {
+ if (drawable != mDrawable) {
+ final Drawable existingDrawable = mDrawable;
+ mDrawable = drawable;
+
+ final boolean skipLayout = drawable != null
+ && existingDrawable != null
+ && existingDrawable.getIntrinsicHeight() == drawable.getIntrinsicHeight()
+ && existingDrawable.getIntrinsicWidth() == drawable.getIntrinsicWidth();
+
+ if (skipLayout) {
+ mDrawable.setBounds(existingDrawable.getBounds());
+ } else {
+ requestLayout();
+ }
+
+ invalidate();
+ }
+ }
+
+ public void setImageResource(int resId) {
+ setImageDrawable(resId == 0 ? null : getContext().getDrawable(resId));
+ }
+
+ public void setImageCirclePercentage(float percentage) {
+ float clamped = Math.max(0, Math.min(1, percentage));
+ if (clamped != mImageCirclePercentage) {
+ mImageCirclePercentage = clamped;
+ invalidate();
+ }
+ }
+
+ public void setImageHorizontalOffcenterPercentage(float percentage) {
+ if (percentage != mImageHorizontalOffcenterPercentage) {
+ mImageHorizontalOffcenterPercentage = percentage;
+ invalidate();
+ }
+ }
+
+ public void setImageTint(int tint) {
+ if (tint != mImageTint) {
+ mImageTint = tint;
+ invalidate();
+ }
+ }
+
+ public float getCircleRadius() {
+ float radius = mCircleRadius;
+ if (mCircleRadius <= 0 && mCircleRadiusPercent > 0) {
+ radius = Math.max(getMeasuredHeight(), getMeasuredWidth()) * mCircleRadiusPercent;
+ }
+
+ return radius - mRadiusInset;
+ }
+
+ public float getCircleRadiusPercent() {
+ return mCircleRadiusPercent;
+ }
+
+ public float getCircleRadiusPressed() {
+ float radius = mCircleRadiusPressed;
+
+ if (mCircleRadiusPressed <= 0 && mCircleRadiusPressedPercent > 0) {
+ radius = Math.max(getMeasuredHeight(), getMeasuredWidth())
+ * mCircleRadiusPressedPercent;
+ }
+
+ return radius - mRadiusInset;
+ }
+
+ public float getCircleRadiusPressedPercent() {
+ return mCircleRadiusPressedPercent;
+ }
+
+ public void setCircleRadius(float circleRadius) {
+ if (circleRadius != mCircleRadius) {
+ mCircleRadius = circleRadius;
+ invalidate();
+ }
+ }
+
+ /**
+ * 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;
+ invalidate();
+ }
+ }
+
+ public void setCircleRadiusPressed(float circleRadiusPressed) {
+ if (circleRadiusPressed != mCircleRadiusPressed) {
+ mCircleRadiusPressed = circleRadiusPressed;
+ invalidate();
+ }
+ }
+
+ /**
+ * 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;
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ setColorForCurrentState();
+ }
+
+ public void setCircleColor(int circleColor) {
+ setCircleColorStateList(ColorStateList.valueOf(circleColor));
+ }
+
+ public void setCircleColorStateList(ColorStateList circleColor) {
+ if (!Objects.equals(circleColor, mCircleColor)) {
+ mCircleColor = circleColor;
+ setColorForCurrentState();
+ invalidate();
+ }
+ }
+
+ public ColorStateList getCircleColorStateList() {
+ return mCircleColor;
+ }
+
+ 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 (show) {
+ mIndeterminateDrawable.startAnimation();
+ } else {
+ mIndeterminateDrawable.stopAnimation();
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (visibility != View.VISIBLE) {
+ showIndeterminateProgress(false);
+ } else if (mProgressIndeterminate) {
+ showIndeterminateProgress(true);
+ }
+ }
+
+ 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 != mShadowVisibility) {
+ mShadowVisibility = 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;
+ invalidate();
+ }
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ super.setPressed(pressed);
+ if (pressed != mPressed) {
+ mPressed = pressed;
+ invalidate();
+ }
+ }
+
+ public Drawable getImageDrawable() {
+ return mDrawable;
+ }
+
+ /**
+ * @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;
+ }
+}