Home | History | Annotate | Download | only in animationsdemo
      1 /*
      2  * Copyright 2012 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.animationsdemo;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.content.Intent;
     24 import android.graphics.Point;
     25 import android.graphics.Rect;
     26 import android.os.Bundle;
     27 import android.support.v4.app.FragmentActivity;
     28 import android.support.v4.app.NavUtils;
     29 import android.view.MenuItem;
     30 import android.view.View;
     31 import android.view.animation.DecelerateInterpolator;
     32 import android.widget.ImageView;
     33 
     34 /**
     35  * A sample showing how to zoom an image thumbnail to full-screen, by animating the bounds of the
     36  * zoomed image from the thumbnail bounds to the screen bounds.
     37  *
     38  * <p>In this sample, the user can touch one of two images. Touching an image zooms it in, covering
     39  * the entire activity content area. Touching the zoomed-in image hides it.</p>
     40  */
     41 public class ZoomActivity extends FragmentActivity {
     42     /**
     43      * Hold a reference to the current animator, so that it can be canceled mid-way.
     44      */
     45     private Animator mCurrentAnimator;
     46 
     47     /**
     48      * The system "short" animation time duration, in milliseconds. This duration is ideal for
     49      * subtle animations or animations that occur very frequently.
     50      */
     51     private int mShortAnimationDuration;
     52 
     53     @Override
     54     protected void onCreate(Bundle savedInstanceState) {
     55         super.onCreate(savedInstanceState);
     56         setContentView(R.layout.activity_zoom);
     57 
     58         // Hook up clicks on the thumbnail views.
     59 
     60         final View thumb1View = findViewById(R.id.thumb_button_1);
     61         thumb1View.setOnClickListener(new View.OnClickListener() {
     62             @Override
     63             public void onClick(View view) {
     64                 zoomImageFromThumb(thumb1View, R.drawable.image1);
     65             }
     66         });
     67 
     68         final View thumb2View = findViewById(R.id.thumb_button_2);
     69         thumb2View.setOnClickListener(new View.OnClickListener() {
     70             @Override
     71             public void onClick(View view) {
     72                 zoomImageFromThumb(thumb2View, R.drawable.image2);
     73             }
     74         });
     75 
     76         // Retrieve and cache the system's default "short" animation time.
     77         mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
     78     }
     79 
     80     @Override
     81     public boolean onOptionsItemSelected(MenuItem item) {
     82         switch (item.getItemId()) {
     83             case android.R.id.home:
     84                 // Navigate "up" the demo structure to the launchpad activity.
     85                 // See http://developer.android.com/design/patterns/navigation.html for more.
     86                 NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
     87                 return true;
     88         }
     89 
     90         return super.onOptionsItemSelected(item);
     91     }
     92 
     93     /**
     94      * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in"
     95      * image view and animating its bounds to fit the entire activity content area. More
     96      * specifically:
     97      *
     98      * <ol>
     99      *   <li>Assign the high-res image to the hidden "zoomed-in" (expanded) image view.</li>
    100      *   <li>Calculate the starting and ending bounds for the expanded view.</li>
    101      *   <li>Animate each of four positioning/sizing properties (X, Y, SCALE_X, SCALE_Y)
    102      *       simultaneously, from the starting bounds to the ending bounds.</li>
    103      *   <li>Zoom back out by running the reverse animation on click.</li>
    104      * </ol>
    105      *
    106      * @param thumbView  The thumbnail view to zoom in.
    107      * @param imageResId The high-resolution version of the image represented by the thumbnail.
    108      */
    109     private void zoomImageFromThumb(final View thumbView, int imageResId) {
    110         // If there's an animation in progress, cancel it immediately and proceed with this one.
    111         if (mCurrentAnimator != null) {
    112             mCurrentAnimator.cancel();
    113         }
    114 
    115         // Load the high-resolution "zoomed-in" image.
    116         final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image);
    117         expandedImageView.setImageResource(imageResId);
    118 
    119         // Calculate the starting and ending bounds for the zoomed-in image. This step
    120         // involves lots of math. Yay, math.
    121         final Rect startBounds = new Rect();
    122         final Rect finalBounds = new Rect();
    123         final Point globalOffset = new Point();
    124 
    125         // The start bounds are the global visible rectangle of the thumbnail, and the
    126         // final bounds are the global visible rectangle of the container view. Also
    127         // set the container view's offset as the origin for the bounds, since that's
    128         // the origin for the positioning animation properties (X, Y).
    129         thumbView.getGlobalVisibleRect(startBounds);
    130         findViewById(R.id.container).getGlobalVisibleRect(finalBounds, globalOffset);
    131         startBounds.offset(-globalOffset.x, -globalOffset.y);
    132         finalBounds.offset(-globalOffset.x, -globalOffset.y);
    133 
    134         // Adjust the start bounds to be the same aspect ratio as the final bounds using the
    135         // "center crop" technique. This prevents undesirable stretching during the animation.
    136         // Also calculate the start scaling factor (the end scaling factor is always 1.0).
    137         float startScale;
    138         if ((float) finalBounds.width() / finalBounds.height()
    139                 > (float) startBounds.width() / startBounds.height()) {
    140             // Extend start bounds horizontally
    141             startScale = (float) startBounds.height() / finalBounds.height();
    142             float startWidth = startScale * finalBounds.width();
    143             float deltaWidth = (startWidth - startBounds.width()) / 2;
    144             startBounds.left -= deltaWidth;
    145             startBounds.right += deltaWidth;
    146         } else {
    147             // Extend start bounds vertically
    148             startScale = (float) startBounds.width() / finalBounds.width();
    149             float startHeight = startScale * finalBounds.height();
    150             float deltaHeight = (startHeight - startBounds.height()) / 2;
    151             startBounds.top -= deltaHeight;
    152             startBounds.bottom += deltaHeight;
    153         }
    154 
    155         // Hide the thumbnail and show the zoomed-in view. When the animation begins,
    156         // it will position the zoomed-in view in the place of the thumbnail.
    157         thumbView.setAlpha(0f);
    158         expandedImageView.setVisibility(View.VISIBLE);
    159 
    160         // Set the pivot point for SCALE_X and SCALE_Y transformations to the top-left corner of
    161         // the zoomed-in view (the default is the center of the view).
    162         expandedImageView.setPivotX(0f);
    163         expandedImageView.setPivotY(0f);
    164 
    165         // Construct and run the parallel animation of the four translation and scale properties
    166         // (X, Y, SCALE_X, and SCALE_Y).
    167         AnimatorSet set = new AnimatorSet();
    168         set
    169                 .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left,
    170                         finalBounds.left))
    171                 .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top,
    172                         finalBounds.top))
    173                 .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))
    174                 .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f));
    175         set.setDuration(mShortAnimationDuration);
    176         set.setInterpolator(new DecelerateInterpolator());
    177         set.addListener(new AnimatorListenerAdapter() {
    178             @Override
    179             public void onAnimationEnd(Animator animation) {
    180                 mCurrentAnimator = null;
    181             }
    182 
    183             @Override
    184             public void onAnimationCancel(Animator animation) {
    185                 mCurrentAnimator = null;
    186             }
    187         });
    188         set.start();
    189         mCurrentAnimator = set;
    190 
    191         // Upon clicking the zoomed-in image, it should zoom back down to the original bounds
    192         // and show the thumbnail instead of the expanded image.
    193         final float startScaleFinal = startScale;
    194         expandedImageView.setOnClickListener(new View.OnClickListener() {
    195             @Override
    196             public void onClick(View view) {
    197                 if (mCurrentAnimator != null) {
    198                     mCurrentAnimator.cancel();
    199                 }
    200 
    201                 // Animate the four positioning/sizing properties in parallel, back to their
    202                 // original values.
    203                 AnimatorSet set = new AnimatorSet();
    204                 set
    205                         .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left))
    206                         .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
    207                         .with(ObjectAnimator
    208                                 .ofFloat(expandedImageView, View.SCALE_X, startScaleFinal))
    209                         .with(ObjectAnimator
    210                                 .ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));
    211                 set.setDuration(mShortAnimationDuration);
    212                 set.setInterpolator(new DecelerateInterpolator());
    213                 set.addListener(new AnimatorListenerAdapter() {
    214                     @Override
    215                     public void onAnimationEnd(Animator animation) {
    216                         thumbView.setAlpha(1f);
    217                         expandedImageView.setVisibility(View.GONE);
    218                         mCurrentAnimator = null;
    219                     }
    220 
    221                     @Override
    222                     public void onAnimationCancel(Animator animation) {
    223                         thumbView.setAlpha(1f);
    224                         expandedImageView.setVisibility(View.GONE);
    225                         mCurrentAnimator = null;
    226                     }
    227                 });
    228                 set.start();
    229                 mCurrentAnimator = set;
    230             }
    231         });
    232     }
    233 }
    234