1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.camera.ui; 18 19 import android.animation.Animator; 20 import android.animation.Animator.AnimatorListener; 21 import android.animation.ValueAnimator; 22 import android.content.res.Resources; 23 import android.graphics.Canvas; 24 import android.graphics.ColorFilter; 25 import android.graphics.Paint; 26 import android.graphics.PixelFormat; 27 import android.graphics.Point; 28 import android.graphics.drawable.Drawable; 29 30 import com.android.camera.util.Gusterpolator; 31 import com.android.camera2.R; 32 33 /** 34 * This class implements a circular drawable that starts with a zero radius 35 * and can be triggered to animate expand to a given radius. 36 * <p> 37 * There are two colors associated with this drawable: 38 * <p> 39 * A background color, which is loaded from a resource 40 * R.color.mode_icon_hover_highlight. 41 * <p> 42 * And, a main color, which is attached to the main circle that is expanded last 43 * and is drawn on top of the other colors. 44 * <p> 45 * The driving purpose for this class is to implement a Material-like look and 46 * feel for mode switcher touch events. 47 */ 48 public class TouchCircleDrawable extends Drawable { 49 private static final int CIRCLE_ANIM_DURATION_MS = 250; 50 51 private Paint mColorPaint = new Paint(); 52 private Paint mBackgroundPaint = new Paint(); 53 private int mColor; 54 private int mColorAlpha = 0xff; 55 private int mColorRadius; 56 private int mBackgroundRadius; 57 private Drawable mIconDrawable; 58 private int mIconDrawableSize; 59 private boolean mDrawBackground; 60 61 private Animator.AnimatorListener mAnimatorListener; 62 private ValueAnimator.AnimatorUpdateListener mUpdateListener; 63 64 private static final int INVALID = -1; 65 private int mW = INVALID; 66 private int mH = INVALID; 67 private Point mCenter; 68 69 /** 70 * Constructor 71 * 72 * @param resources Resources, needed to poke around for the background 73 * color value. 74 * @param color The main this circle drawable expands to. 75 * @param baseColor The color of the initial expanded circle 76 * (draws behind the main color). 77 */ TouchCircleDrawable(Resources resources, int color, int baseColor)78 public TouchCircleDrawable(Resources resources, int color, int baseColor) { 79 super(); 80 81 mColorPaint.setAntiAlias(true); 82 mBackgroundPaint.setAntiAlias(true); 83 mBackgroundPaint.setColor(resources.getColor(R.color.mode_icon_hover_highlight)); 84 85 setColor(color); 86 } 87 88 /** 89 * Constructor 90 * 91 * @param resources Resources, needed to poke around for the background color value. 92 */ TouchCircleDrawable(Resources resources)93 public TouchCircleDrawable(Resources resources) { 94 this(resources, 0xffffff, 0xffffff); 95 } 96 97 /** 98 * Set the size of this drawable. 99 * 100 * @param w Width to set. 101 * @param h Height to set. 102 */ setSize(int w, int h)103 public void setSize(int w, int h) { 104 mW = w; 105 mH = h; 106 } 107 108 /** 109 * Set the center of the circle for this drawable. 110 * 111 * @param p The center point. 112 */ setCenter(Point p)113 public void setCenter(Point p) { 114 mCenter = p; 115 updateIconBounds(); 116 } 117 118 /** 119 * @return The center of this drawable. 120 */ getCenter()121 public Point getCenter() { 122 return mCenter; 123 } 124 125 @Override draw(Canvas canvas)126 public void draw(Canvas canvas) { 127 int w = mW; 128 int h = mH; 129 130 if (w == INVALID || h == INVALID || mCenter == null) { 131 return; 132 } 133 134 if (mDrawBackground) { 135 canvas.drawCircle(mCenter.x, mCenter.y, mBackgroundRadius, mBackgroundPaint); 136 } 137 canvas.drawCircle(mCenter.x, mCenter.y, mColorRadius, mColorPaint); 138 if (mIconDrawable != null) { 139 mIconDrawable.draw(canvas); 140 } 141 } 142 143 @Override setAlpha(int alpha)144 public void setAlpha(int alpha) { 145 mColorAlpha = alpha; 146 } 147 148 @Override setColorFilter(ColorFilter cf)149 public void setColorFilter(ColorFilter cf) { 150 mColorPaint.setColorFilter(cf); 151 } 152 153 @Override getOpacity()154 public int getOpacity() { 155 return PixelFormat.TRANSLUCENT; 156 } 157 158 /** 159 * Set the main color. 160 * 161 * @param color The main color. 162 */ setColor(int color)163 public void setColor(int color) { 164 mColor = color; 165 mColorPaint.setColor(mColor); 166 mColorPaint.setAlpha(mColorAlpha); 167 } 168 setIconDrawable(Drawable d, int size)169 public void setIconDrawable(Drawable d, int size) { 170 mIconDrawable = d; 171 mIconDrawableSize = size; 172 updateIconBounds(); 173 } 174 updateIconBounds()175 private void updateIconBounds() { 176 if (mCenter != null) { 177 mIconDrawable.setBounds( 178 mCenter.x - mIconDrawableSize/2, mCenter.y - mIconDrawableSize/2, 179 mCenter.x + mIconDrawableSize/2, mCenter.y + mIconDrawableSize/2); 180 } 181 } 182 183 /** 184 * Start the expand animation. 185 */ animate()186 public void animate() { 187 mBackgroundRadius = Math.min(mW/2, mH/2); 188 189 final ValueAnimator colorAnimator = 190 ValueAnimator.ofInt(0, Math.min(mW/2, mH/2)); 191 colorAnimator.setDuration(CIRCLE_ANIM_DURATION_MS); 192 colorAnimator.setInterpolator(Gusterpolator.INSTANCE); 193 colorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 194 @Override 195 public void onAnimationUpdate(ValueAnimator animation) { 196 mColorRadius = (Integer) animation.getAnimatedValue(); 197 invalidateSelf(); 198 if (mUpdateListener != null) { 199 mUpdateListener.onAnimationUpdate(animation); 200 } 201 } 202 }); 203 204 colorAnimator.addListener(new AnimatorListener() { 205 @Override 206 public void onAnimationStart(Animator animation) { 207 mDrawBackground = true; 208 209 if (mAnimatorListener != null) { 210 mAnimatorListener.onAnimationStart(animation); 211 } 212 } 213 214 @Override 215 public void onAnimationEnd(Animator animation) { 216 mDrawBackground = false; 217 218 if (mAnimatorListener != null) { 219 mAnimatorListener.onAnimationEnd(animation); 220 } 221 } 222 223 @Override 224 public void onAnimationCancel(Animator animation) { 225 mDrawBackground = false; 226 227 if (mAnimatorListener != null) { 228 mAnimatorListener.onAnimationCancel(animation); 229 } 230 } 231 232 @Override 233 public void onAnimationRepeat(Animator animation) { 234 if (mAnimatorListener != null) { 235 mAnimatorListener.onAnimationRepeat(animation); 236 } 237 } 238 }); 239 240 colorAnimator.start(); 241 } 242 243 /** 244 * Reset this drawable to its initial, preanimated state. 245 */ reset()246 public void reset() { 247 mColorRadius = 0; 248 } 249 250 /** 251 * Set an {@link android.animation.Animator.AnimatorListener} to be 252 * attached to the animation. 253 * 254 * @param listener The listener. 255 */ setAnimatorListener(Animator.AnimatorListener listener)256 public void setAnimatorListener(Animator.AnimatorListener listener) { 257 mAnimatorListener = listener; 258 } 259 260 /** 261 * Set an {@link android.animation.ValueAnimator} to be 262 * attached to the animation. 263 * 264 * @param listener The listener. 265 */ setUpdateListener(ValueAnimator.AnimatorUpdateListener listener)266 public void setUpdateListener(ValueAnimator.AnimatorUpdateListener listener) { 267 mUpdateListener = listener; 268 } 269 } 270