Home | History | Annotate | Download | only in activityanim
      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
     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      */
    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      */
    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
    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      */
    267     public void setSaturation(float value) {
    268         colorizerMatrix.setSaturation(value);
    269         ColorMatrixColorFilter colorizerFilter = new ColorMatrixColorFilter(colorizerMatrix);
    270         mBitmapDrawable.setColorFilter(colorizerFilter);
    271     }
    272 
    273     @Override
    274     public void finish() {
    275         super.finish();
    276 
    277         // override transitions to skip the standard window animations
    278         overridePendingTransition(0, 0);
    279     }
    280 }
    281