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
17package com.android.camera.widget;
18
19import android.animation.Animator;
20import android.animation.AnimatorSet;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.drawable.BitmapDrawable;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.animation.AccelerateInterpolator;
31import android.view.animation.DecelerateInterpolator;
32import android.widget.ImageView;
33
34import com.android.camera.util.CameraUtil;
35import com.android.camera2.R;
36
37/**
38 * An ImageView which has the built-in peek animation support.
39 */
40public 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
56    public PeekView(Context context) {
57        super(context);
58        init();
59    }
60
61    public PeekView(Context context, AttributeSet attrs) {
62        super(context, attrs);
63        init();
64    }
65
66    public PeekView(Context context, AttributeSet attrs, int defStyle) {
67        super(context, attrs, defStyle);
68        init();
69    }
70
71    private void init() {
72        mRotationPivot = new Point();
73    }
74
75    @Override
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     */
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     */
162    public boolean isPeekAnimationRunning() {
163        return mPeekAnimator.isRunning();
164    }
165
166    /**
167     * Stops the animation. See {@link android.animation.Animator#end()}.
168     */
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     */
180    public void cancelPeekAnimation() {
181        if (isPeekAnimationRunning()) {
182            mPeekAnimator.cancel();
183        } else {
184            clear();
185        }
186    }
187
188    private void clear() {
189        setVisibility(INVISIBLE);
190        setImageDrawable(null);
191    }
192}
193