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