1 /*
2  * Copyright (C) 2013 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.example.android.activityanim;
18 
19 import android.animation.ObjectAnimator;
20 import android.animation.TimeInterpolator;
21 import android.app.Activity;
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 import android.graphics.ColorMatrix;
25 import android.graphics.ColorMatrixColorFilter;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.ColorDrawable;
28 import android.os.Bundle;
29 import android.view.View;
30 import android.view.ViewTreeObserver;
31 import android.view.animation.AccelerateInterpolator;
32 import android.view.animation.DecelerateInterpolator;
33 import android.widget.FrameLayout;
34 import android.widget.ImageView;
35 import android.widget.TextView;
36 
37 /**
38  * This sub-activity shows a zoomed-in view of a specific photo, along with the
39  * picture's text description. Most of the logic is for the animations that will
40  * be run when the activity is being launched and exited. When launching,
41  * the large version of the picture will resize from the thumbnail version in the
42  * main activity, colorizing it from the thumbnail's grayscale version at the
43  * same time. Meanwhile, the black background of the activity will fade in and
44  * the description will eventually slide into place. The exit animation runs all
45  * of this in reverse.
46  *
47  */
48 public class PictureDetailsActivity extends Activity {
49 
50     private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
51     private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
52     private static final String PACKAGE_NAME = "com.example.android.activityanim";
53     private static final int ANIM_DURATION = 500;
54 
55     private BitmapDrawable mBitmapDrawable;
56     private ColorMatrix colorizerMatrix = new ColorMatrix();
57     ColorDrawable mBackground;
58     int mLeftDelta;
59     int mTopDelta;
60     float mWidthScale;
61     float mHeightScale;
62     private ImageView mImageView;
63     private TextView mTextView;
64     private FrameLayout mTopLevelLayout;
65     private ShadowLayout mShadowLayout;
66     private int mOriginalOrientation;
67 
68     @Override
onCreate(Bundle savedInstanceState)69     public void onCreate(Bundle savedInstanceState) {
70         super.onCreate(savedInstanceState);
71         setContentView(R.layout.picture_info);
72         mImageView = (ImageView) findViewById(R.id.imageView);
73         mTopLevelLayout = (FrameLayout) findViewById(R.id.topLevelLayout);
74         mShadowLayout = (ShadowLayout) findViewById(R.id.shadowLayout);
75         mTextView = (TextView) findViewById(R.id.description);
76 
77         // Retrieve the data we need for the picture/description to display and
78         // the thumbnail to animate it from
79         Bundle bundle = getIntent().getExtras();
80         Bitmap bitmap = BitmapUtils.getBitmap(getResources(),
81                 bundle.getInt(PACKAGE_NAME + ".resourceId"));
82         String description = bundle.getString(PACKAGE_NAME + ".description");
83         final int thumbnailTop = bundle.getInt(PACKAGE_NAME + ".top");
84         final int thumbnailLeft = bundle.getInt(PACKAGE_NAME + ".left");
85         final int thumbnailWidth = bundle.getInt(PACKAGE_NAME + ".width");
86         final int thumbnailHeight = bundle.getInt(PACKAGE_NAME + ".height");
87         mOriginalOrientation = bundle.getInt(PACKAGE_NAME + ".orientation");
88 
89         mBitmapDrawable = new BitmapDrawable(getResources(), bitmap);
90         mImageView.setImageDrawable(mBitmapDrawable);
91         mTextView.setText(description);
92 
93         mBackground = new ColorDrawable(Color.BLACK);
94         mTopLevelLayout.setBackground(mBackground);
95 
96         // Only run the animation if we're coming from the parent activity, not if
97         // we're recreated automatically by the window manager (e.g., device rotation)
98         if (savedInstanceState == null) {
99             ViewTreeObserver observer = mImageView.getViewTreeObserver();
100             observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
101 
102                 @Override
103                 public boolean onPreDraw() {
104                     mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
105 
106                     // Figure out where the thumbnail and full size versions are, relative
107                     // to the screen and each other
108                     int[] screenLocation = new int[2];
109                     mImageView.getLocationOnScreen(screenLocation);
110                     mLeftDelta = thumbnailLeft - screenLocation[0];
111                     mTopDelta = thumbnailTop - screenLocation[1];
112 
113                     // Scale factors to make the large version the same size as the thumbnail
114                     mWidthScale = (float) thumbnailWidth / mImageView.getWidth();
115                     mHeightScale = (float) thumbnailHeight / mImageView.getHeight();
116 
117                     runEnterAnimation();
118 
119                     return true;
120                 }
121             });
122         }
123     }
124 
125     /**
126      * The enter animation scales the picture in from its previous thumbnail
127      * size/location, colorizing it in parallel. In parallel, the background of the
128      * activity is fading in. When the pictue is in place, the text description
129      * drops down.
130      */
runEnterAnimation()131     public void runEnterAnimation() {
132         final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale);
133 
134         // Set starting values for properties we're going to animate. These
135         // values scale and position the full size version down to the thumbnail
136         // size/location, from which we'll animate it back up
137         mImageView.setPivotX(0);
138         mImageView.setPivotY(0);
139         mImageView.setScaleX(mWidthScale);
140         mImageView.setScaleY(mHeightScale);
141         mImageView.setTranslationX(mLeftDelta);
142         mImageView.setTranslationY(mTopDelta);
143 
144         // We'll fade the text in later
145         mTextView.setAlpha(0);
146 
147         // Animate scale and translation to go from thumbnail to full size
148         mImageView.animate().setDuration(duration).
149                 scaleX(1).scaleY(1).
150                 translationX(0).translationY(0).
151                 setInterpolator(sDecelerator).
152                 withEndAction(new Runnable() {
153                     public void run() {
154                         // Animate the description in after the image animation
155                         // is done. Slide and fade the text in from underneath
156                         // the picture.
157                         mTextView.setTranslationY(-mTextView.getHeight());
158                         mTextView.animate().setDuration(duration/2).
159                                 translationY(0).alpha(1).
160                                 setInterpolator(sDecelerator);
161                     }
162                 });
163 
164         // Fade in the black background
165         ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
166         bgAnim.setDuration(duration);
167         bgAnim.start();
168 
169         // Animate a color filter to take the image from grayscale to full color.
170         // This happens in parallel with the image scaling and moving into place.
171         ObjectAnimator colorizer = ObjectAnimator.ofFloat(PictureDetailsActivity.this,
172                 "saturation", 0, 1);
173         colorizer.setDuration(duration);
174         colorizer.start();
175 
176         // Animate a drop-shadow of the image
177         ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout, "shadowDepth", 0, 1);
178         shadowAnim.setDuration(duration);
179         shadowAnim.start();
180     }
181 
182     /**
183      * The exit animation is basically a reverse of the enter animation, except that if
184      * the orientation has changed we simply scale the picture back into the center of
185      * the screen.
186      *
187      * @param endAction This action gets run after the animation completes (this is
188      * when we actually switch activities)
189      */
runExitAnimation(final Runnable endAction)190     public void runExitAnimation(final Runnable endAction) {
191         final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale);
192 
193         // No need to set initial values for the reverse animation; the image is at the
194         // starting size/location that we want to start from. Just animate to the
195         // thumbnail size/location that we retrieved earlier
196 
197         // Caveat: configuration change invalidates thumbnail positions; just animate
198         // the scale around the center. Also, fade it out since it won't match up with
199         // whatever's actually in the center
200         final boolean fadeOut;
201         if (getResources().getConfiguration().orientation != mOriginalOrientation) {
202             mImageView.setPivotX(mImageView.getWidth() / 2);
203             mImageView.setPivotY(mImageView.getHeight() / 2);
204             mLeftDelta = 0;
205             mTopDelta = 0;
206             fadeOut = true;
207         } else {
208             fadeOut = false;
209         }
210 
211         // First, slide/fade text out of the way
212         mTextView.animate().translationY(-mTextView.getHeight()).alpha(0).
213                 setDuration(duration/2).setInterpolator(sAccelerator).
214                 withEndAction(new Runnable() {
215                     public void run() {
216                         // Animate image back to thumbnail size/location
217                         mImageView.animate().setDuration(duration).
218                                 scaleX(mWidthScale).scaleY(mHeightScale).
219                                 translationX(mLeftDelta).translationY(mTopDelta).
220                                 withEndAction(endAction);
221                         if (fadeOut) {
222                             mImageView.animate().alpha(0);
223                         }
224                         // Fade out background
225                         ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0);
226                         bgAnim.setDuration(duration);
227                         bgAnim.start();
228 
229                         // Animate the shadow of the image
230                         ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout,
231                                 "shadowDepth", 1, 0);
232                         shadowAnim.setDuration(duration);
233                         shadowAnim.start();
234 
235                         // Animate a color filter to take the image back to grayscale,
236                         // in parallel with the image scaling and moving into place.
237                         ObjectAnimator colorizer =
238                                 ObjectAnimator.ofFloat(PictureDetailsActivity.this,
239                                 "saturation", 1, 0);
240                         colorizer.setDuration(duration);
241                         colorizer.start();
242                     }
243                 });
244 
245 
246     }
247 
248     /**
249      * Overriding this method allows us to run our exit animation first, then exiting
250      * the activity when it is complete.
251      */
252     @Override
onBackPressed()253     public void onBackPressed() {
254         runExitAnimation(new Runnable() {
255             public void run() {
256                 // *Now* go ahead and exit the activity
257                 finish();
258             }
259         });
260     }
261 
262     /**
263      * This is called by the colorizing animator. It sets a saturation factor that is then
264      * passed onto a filter on the picture's drawable.
265      * @param value
266      */
setSaturation(float value)267     public void setSaturation(float value) {
268         colorizerMatrix.setSaturation(value);
269         ColorMatrixColorFilter colorizerFilter = new ColorMatrixColorFilter(colorizerMatrix);
270         mBitmapDrawable.setColorFilter(colorizerFilter);
271     }
272 
273     @Override
finish()274     public void finish() {
275         super.finish();
276 
277         // override transitions to skip the standard window animations
278         overridePendingTransition(0, 0);
279     }
280 }
281