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.widget;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.view.animation.AccelerateInterpolator;
31 import android.view.animation.DecelerateInterpolator;
32 import android.widget.ImageView;
33 
34 import com.android.camera.util.CameraUtil;
35 import com.android.camera2.R;
36 
37 /**
38  * An ImageView which has the built-in peek animation support.
39  */
40 public class PeekView extends ImageView {
41 
42     private static final float ROTATE_ANGLE = -7f;
43     private static final long PEEK_IN_DURATION_MS = 200;
44     private static final long PEEK_STAY_DURATION_MS = 100;
45     private static final long PEEK_OUT_DURATION_MS = 200;
46     private static final float FILMSTRIP_SCALE = 0.7f;
47 
48     private AnimatorSet mPeekAnimator;
49     private float mPeekRotateAngle;
50     private Point mRotationPivot;
51     private float mRotateScale;
52     private boolean mAnimationCanceled;
53     private Drawable mImageDrawable;
54     private Rect mDrawableBound;
55 
PeekView(Context context)56     public PeekView(Context context) {
57         super(context);
58         init();
59     }
60 
PeekView(Context context, AttributeSet attrs)61     public PeekView(Context context, AttributeSet attrs) {
62         super(context, attrs);
63         init();
64     }
65 
PeekView(Context context, AttributeSet attrs, int defStyle)66     public PeekView(Context context, AttributeSet attrs, int defStyle) {
67         super(context, attrs, defStyle);
68         init();
69     }
70 
init()71     private void init() {
72         mRotationPivot = new Point();
73     }
74 
75     @Override
onDraw(Canvas c)76     protected void onDraw(Canvas c) {
77         super.onDraw(c);
78         if (mImageDrawable == null) {
79             return;
80         }
81         c.save();
82         c.rotate(mPeekRotateAngle, mRotationPivot.x, mRotationPivot.y);
83         mImageDrawable.setBounds(mDrawableBound);
84         mImageDrawable.draw(c);
85         c.restore();
86     }
87 
88     /**
89      * Starts the peek animation.
90      *
91      * @param bitmap The bitmap for the animation.
92      * @param strong {@code true} if the animation is the strong version which
93      *               shows more portion of the bitmap.
94      * @param accessibilityString An accessibility String to be announced
95                      during the peek animation.
96      */
startPeekAnimation(final Bitmap bitmap, boolean strong, String accessibilityString)97     public void startPeekAnimation(final Bitmap bitmap, boolean strong,
98             String accessibilityString) {
99         ValueAnimator.AnimatorUpdateListener updateListener =
100                 new ValueAnimator.AnimatorUpdateListener() {
101                     @Override
102                     public void onAnimationUpdate(ValueAnimator valueAnimator) {
103                         mPeekRotateAngle = mRotateScale * (Float) valueAnimator.getAnimatedValue();
104                         invalidate();
105                     }
106                 };
107         ValueAnimator peekAnimateIn = ValueAnimator.ofFloat(0f, ROTATE_ANGLE);
108         ValueAnimator peekAnimateStay = ValueAnimator.ofFloat(ROTATE_ANGLE, ROTATE_ANGLE);
109         ValueAnimator peekAnimateOut = ValueAnimator.ofFloat(ROTATE_ANGLE, 0f);
110         peekAnimateIn.addUpdateListener(updateListener);
111         peekAnimateOut.addUpdateListener(updateListener);
112         peekAnimateIn.setDuration(PEEK_IN_DURATION_MS);
113         peekAnimateStay.setDuration(PEEK_STAY_DURATION_MS);
114         peekAnimateOut.setDuration(PEEK_OUT_DURATION_MS);
115         peekAnimateIn.setInterpolator(new DecelerateInterpolator());
116         peekAnimateOut.setInterpolator(new AccelerateInterpolator());
117         mPeekAnimator = new AnimatorSet();
118         mPeekAnimator.playSequentially(peekAnimateIn, peekAnimateStay, peekAnimateOut);
119         mPeekAnimator.addListener(new Animator.AnimatorListener() {
120             @Override
121             public void onAnimationStart(Animator animator) {
122                 setVisibility(VISIBLE);
123                 mAnimationCanceled = false;
124                 invalidate();
125             }
126 
127             @Override
128             public void onAnimationEnd(Animator animator) {
129                 if (!mAnimationCanceled) {
130                     clear();
131                 }
132             }
133 
134             @Override
135             public void onAnimationCancel(Animator animator) {
136                 mAnimationCanceled = true;
137             }
138 
139             @Override
140             public void onAnimationRepeat(Animator animator) {
141 
142             }
143         });
144 
145         mRotateScale = (strong ? 1.0f : 0.5f);
146         mImageDrawable = new BitmapDrawable(getResources(), bitmap);
147         Point drawDim = CameraUtil.resizeToFill(mImageDrawable.getIntrinsicWidth(),
148                 mImageDrawable.getIntrinsicHeight(), 0, (int) (getWidth() * FILMSTRIP_SCALE),
149                 (int) (getHeight() * FILMSTRIP_SCALE));
150         int x = getMeasuredWidth();
151         int y = (getMeasuredHeight() - drawDim.y) / 2;
152         mDrawableBound = new Rect(x, y, x + drawDim.x, y + drawDim.y);
153         mRotationPivot.set(x, (int) (y + drawDim.y * 1.1));
154         mPeekAnimator.start();
155 
156         announceForAccessibility(accessibilityString);
157     }
158 
159     /**
160      * @return whether the animation is running.
161      */
isPeekAnimationRunning()162     public boolean isPeekAnimationRunning() {
163         return mPeekAnimator.isRunning();
164     }
165 
166     /**
167      * Stops the animation. See {@link android.animation.Animator#end()}.
168      */
stopPeekAnimation()169     public void stopPeekAnimation() {
170         if (isPeekAnimationRunning()) {
171             mPeekAnimator.end();
172         } else {
173             clear();
174         }
175     }
176 
177     /**
178      * Cancels the animation. See {@link android.animation.Animator#cancel()}.
179      */
cancelPeekAnimation()180     public void cancelPeekAnimation() {
181         if (isPeekAnimationRunning()) {
182             mPeekAnimator.cancel();
183         } else {
184             clear();
185         }
186     }
187 
clear()188     private void clear() {
189         setVisibility(INVISIBLE);
190         setImageDrawable(null);
191     }
192 }
193