Home | History | Annotate | Download | only in animation
      1 page.title=Zooming a View
      2 trainingnavtop=true
      3 
      4 @jd:body
      5 
      6     <div id="tb-wrapper">
      7       <div id="tb">
      8         <h2>
      9           This lesson teaches you to:
     10         </h2>
     11         <ol>
     12           <li>
     13             <a href="#views">Create the Views</a>
     14           </li>
     15           <li>
     16             <a href="#setup">Set up the Zoom Animation</a>
     17           </li>
     18           <li>
     19             <a href="#animate">Zoom the View</a>
     20           </li>
     21         </ol>
     22         <h2>
     23           Try it out
     24         </h2>
     25         <div class="download-box">
     26           <a href="{@docRoot}shareables/training/Animations.zip" class=
     27           "button">Download the sample app</a>
     28           <p class="filename">
     29             Animations.zip
     30           </p>
     31         </div>
     32       </div>
     33     </div>
     34     <p>
     35       This lesson demonstrates how to do a touch-to-zoom animation, which is useful for apps such as photo
     36       galleries to animate a view from a thumbnail to a full-size image that fills the screen.
     37     </p>
     38     <p>Here's what a touch-to-zoom animation looks like that
     39       expands an image thumbnail to fill the screen:
     40     </p>
     41 
     42     <div class="framed-galaxynexus-land-span-8">
     43       <video class="play-on-hover" autoplay>
     44         <source src="anim_zoom.mp4" type="video/mp4">
     45         <source src="anim_zoom.webm" type="video/webm">
     46         <source src="anim_zoom.ogv" type="video/ogg">
     47       </video>
     48     </div>
     49     <div class="figure-caption">
     50       Zoom animation
     51       <div class="video-instructions">&nbsp;</div>
     52     </div>
     53 
     54     <p>
     55       If you want to jump ahead and see a full working example,
     56       <a href="{@docRoot}shareables/training/Animations.zip">download</a> and
     57       run the sample app and select the
     58       Zoom example. See the following files for the code implementation:
     59     </p>
     60     <ul>
     61       <li>
     62         <code>src/TouchHighlightImageButton.java</code> (a simple helper class that shows a blue
     63         touch highlight when the image button is pressed)
     64       </li>
     65       <li>
     66         <code>src/ZoomActivity.java</code>
     67       </li>
     68       <li>
     69         <code>layout/activity_zoom.xml</code>
     70       </li>
     71     </ul>
     72     <h2 id="views">
     73       Create the Views
     74     </h2>
     75     <p>
     76       Create a layout file that contains the small and large version of the content that you want
     77       to zoom. The following example creates an {@link android.widget.ImageButton} for clickable image thumbnail
     78       and an {@link android.widget.ImageView} that displays the enlarged view of the image:
     79     </p>
     80     <pre>
     81 &lt;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     82     android:id="@+id/container"
     83     android:layout_width="match_parent"
     84     android:layout_height="match_parent"&gt;
     85 
     86     &lt;LinearLayout android:layout_width="match_parent"
     87         android:layout_height="wrap_content"
     88         android:orientation="vertical"
     89         android:padding="16dp"&gt;
     90 
     91         &lt;ImageButton
     92             android:id="@+id/thumb_button_1"
     93             android:layout_width="100dp"
     94             android:layout_height="75dp"
     95             android:layout_marginRight="1dp"
     96             android:src="@drawable/thumb1"
     97             android:scaleType="centerCrop"
     98             android:contentDescription="@string/description_image_1" /&gt;
     99 
    100     &lt;/LinearLayout&gt;
    101 
    102     &lt;!-- This initially-hidden ImageView will hold the expanded/zoomed version of
    103          the images above. Without transformations applied, it takes up the entire
    104          screen. To achieve the "zoom" animation, this view's bounds are animated
    105          from the bounds of the thumbnail button above, to its final laid-out
    106          bounds.
    107          --&gt;
    108 
    109     &lt;ImageView
    110         android:id="@+id/expanded_image"
    111         android:layout_width="match_parent"
    112         android:layout_height="match_parent"
    113         android:visibility="invisible"
    114         android:contentDescription="@string/description_zoom_touch_close" /&gt;
    115 
    116 &lt;/FrameLayout&gt;
    117 </pre>
    118     <h2 id="setup">
    119       Set up the Zoom Animation
    120     </h2>
    121     <p>
    122       Once you apply your layout, set up the event handlers that trigger the zoom animation.
    123       The following example adds a {@link android.view.View.OnClickListener} to the {@link
    124       android.widget.ImageButton} to execute the zoom animation when the user
    125       clicks the image button:
    126     </p>
    127     <pre>
    128 public class ZoomActivity extends FragmentActivity {
    129     // Hold a reference to the current animator,
    130     // so that it can be canceled mid-way.
    131     private Animator mCurrentAnimator;
    132 
    133     // The system "short" animation time duration, in milliseconds. This
    134     // duration is ideal for subtle animations or animations that occur
    135     // very frequently.
    136     private int mShortAnimationDuration;
    137 
    138     &#64;Override
    139     protected void onCreate(Bundle savedInstanceState) {
    140         super.onCreate(savedInstanceState);
    141         setContentView(R.layout.activity_zoom);
    142 
    143         // Hook up clicks on the thumbnail views.
    144 
    145         final View thumb1View = findViewById(R.id.thumb_button_1);
    146         thumb1View.setOnClickListener(new View.OnClickListener() {
    147             &#64;Override
    148             public void onClick(View view) {
    149                 zoomImageFromThumb(thumb1View, R.drawable.image1);
    150             }
    151         });
    152 
    153         // Retrieve and cache the system's default "short" animation time.
    154         mShortAnimationDuration = getResources().getInteger(
    155                 android.R.integer.config_shortAnimTime);
    156     }
    157     ...
    158 }
    159 </pre>
    160     <h2 id="animate">
    161       Zoom the View
    162     </h2>
    163     <p>
    164       You'll now need to animate from the normal sized view to the zoomed view
    165       when appropriate. In general, you need to animate from the bounds of the normal-sized view to the
    166       bounds of the larger-sized view. The following method shows you how to implement a zoom animation that
    167       zooms from an image thumbnail to an enlarged view by doing the following things:
    168     </p>
    169     <ol>
    170       <li>Assign the high-res image to the hidden "zoomed-in" (enlarged) {@link
    171       android.widget.ImageView}. The following example loads a large image resource on the UI
    172       thread for simplicity. You will want to do this loading in a separate thread to prevent
    173       blocking on the UI thread and then set the bitmap on the UI thread. Ideally, the bitmap
    174       should not be larger than the screen size.
    175       </li>
    176       <li>Calculate the starting and ending bounds for the {@link android.widget.ImageView}.
    177       </li>
    178       <li>Animate each of the four positioning and sizing properties <code>{@link
    179       android.view.View#X}</code>, <code>{@link android.view.View#Y}</code>, ({@link
    180       android.view.View#SCALE_X}, and <code>{@link android.view.View#SCALE_Y}</code>)
    181       simultaneously, from the starting bounds to the ending bounds. These four animations are
    182       added to an {@link android.animation.AnimatorSet} so that they can be started at the same
    183       time.
    184       </li>
    185       <li>Zoom back out by running a similar animation but in reverse when the user touches the
    186       screen when the image is zoomed in. You can do this by adding a {@link
    187       android.view.View.OnClickListener} to the {@link android.widget.ImageView}. When clicked, the
    188       {@link android.widget.ImageView} minimizes back down to the size of the image thumbnail and
    189       sets its visibility to {@link android.view.View#GONE} to hide it.
    190       </li>
    191     </ol>
    192     <pre>
    193 private void zoomImageFromThumb(final View thumbView, int imageResId) {
    194     // If there's an animation in progress, cancel it
    195     // immediately and proceed with this one.
    196     if (mCurrentAnimator != null) {
    197         mCurrentAnimator.cancel();
    198     }
    199 
    200     // Load the high-resolution "zoomed-in" image.
    201     final ImageView expandedImageView = (ImageView) findViewById(
    202             R.id.expanded_image);
    203     expandedImageView.setImageResource(imageResId);
    204 
    205     // Calculate the starting and ending bounds for the zoomed-in image.
    206     // This step involves lots of math. Yay, math.
    207     final Rect startBounds = new Rect();
    208     final Rect finalBounds = new Rect();
    209     final Point globalOffset = new Point();
    210 
    211     // The start bounds are the global visible rectangle of the thumbnail,
    212     // and the final bounds are the global visible rectangle of the container
    213     // view. Also set the container view's offset as the origin for the
    214     // bounds, since that's the origin for the positioning animation
    215     // properties (X, Y).
    216     thumbView.getGlobalVisibleRect(startBounds);
    217     findViewById(R.id.container)
    218             .getGlobalVisibleRect(finalBounds, globalOffset);
    219     startBounds.offset(-globalOffset.x, -globalOffset.y);
    220     finalBounds.offset(-globalOffset.x, -globalOffset.y);
    221 
    222     // Adjust the start bounds to be the same aspect ratio as the final
    223     // bounds using the "center crop" technique. This prevents undesirable
    224     // stretching during the animation. Also calculate the start scaling
    225     // factor (the end scaling factor is always 1.0).
    226     float startScale;
    227     if ((float) finalBounds.width() / finalBounds.height()
    228             &gt; (float) startBounds.width() / startBounds.height()) {
    229         // Extend start bounds horizontally
    230         startScale = (float) startBounds.height() / finalBounds.height();
    231         float startWidth = startScale * finalBounds.width();
    232         float deltaWidth = (startWidth - startBounds.width()) / 2;
    233         startBounds.left -= deltaWidth;
    234         startBounds.right += deltaWidth;
    235     } else {
    236         // Extend start bounds vertically
    237         startScale = (float) startBounds.width() / finalBounds.width();
    238         float startHeight = startScale * finalBounds.height();
    239         float deltaHeight = (startHeight - startBounds.height()) / 2;
    240         startBounds.top -= deltaHeight;
    241         startBounds.bottom += deltaHeight;
    242     }
    243 
    244     // Hide the thumbnail and show the zoomed-in view. When the animation
    245     // begins, it will position the zoomed-in view in the place of the
    246     // thumbnail.
    247     thumbView.setAlpha(0f);
    248     expandedImageView.setVisibility(View.VISIBLE);
    249 
    250     // Set the pivot point for SCALE_X and SCALE_Y transformations
    251     // to the top-left corner of the zoomed-in view (the default
    252     // is the center of the view).
    253     expandedImageView.setPivotX(0f);
    254     expandedImageView.setPivotY(0f);
    255 
    256     // Construct and run the parallel animation of the four translation and
    257     // scale properties (X, Y, SCALE_X, and SCALE_Y).
    258     AnimatorSet set = new AnimatorSet();
    259     set
    260             .play(ObjectAnimator.ofFloat(expandedImageView, View.X,
    261                     startBounds.left, finalBounds.left))
    262             .with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
    263                     startBounds.top, finalBounds.top))
    264             .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
    265             startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,
    266                     View.SCALE_Y, startScale, 1f));
    267     set.setDuration(mShortAnimationDuration);
    268     set.setInterpolator(new DecelerateInterpolator());
    269     set.addListener(new AnimatorListenerAdapter() {
    270         &#64;Override
    271         public void onAnimationEnd(Animator animation) {
    272             mCurrentAnimator = null;
    273         }
    274 
    275         &#64;Override
    276         public void onAnimationCancel(Animator animation) {
    277             mCurrentAnimator = null;
    278         }
    279     });
    280     set.start();
    281     mCurrentAnimator = set;
    282 
    283     // Upon clicking the zoomed-in image, it should zoom back down
    284     // to the original bounds and show the thumbnail instead of
    285     // the expanded image.
    286     final float startScaleFinal = startScale;
    287     expandedImageView.setOnClickListener(new View.OnClickListener() {
    288         &#64;Override
    289         public void onClick(View view) {
    290             if (mCurrentAnimator != null) {
    291                 mCurrentAnimator.cancel();
    292             }
    293 
    294             // Animate the four positioning/sizing properties in parallel,
    295             // back to their original values.
    296             AnimatorSet set = new AnimatorSet();
    297             set.play(ObjectAnimator
    298                         .ofFloat(expandedImageView, View.X, startBounds.left))
    299                         .with(ObjectAnimator
    300                                 .ofFloat(expandedImageView,
    301                                         View.Y,startBounds.top))
    302                         .with(ObjectAnimator
    303                                 .ofFloat(expandedImageView,
    304                                         View.SCALE_X, startScaleFinal))
    305                         .with(ObjectAnimator
    306                                 .ofFloat(expandedImageView,
    307                                         View.SCALE_Y, startScaleFinal));
    308             set.setDuration(mShortAnimationDuration);
    309             set.setInterpolator(new DecelerateInterpolator());
    310             set.addListener(new AnimatorListenerAdapter() {
    311                 &#64;Override
    312                 public void onAnimationEnd(Animator animation) {
    313                     thumbView.setAlpha(1f);
    314                     expandedImageView.setVisibility(View.GONE);
    315                     mCurrentAnimator = null;
    316                 }
    317 
    318                 &#64;Override
    319                 public void onAnimationCancel(Animator animation) {
    320                     thumbView.setAlpha(1f);
    321                     expandedImageView.setVisibility(View.GONE);
    322                     mCurrentAnimator = null;
    323                 }
    324             });
    325             set.start();
    326             mCurrentAnimator = set;
    327         }
    328     });
    329 }
    330 </pre>