1page.title=Zooming a View
2trainingnavtop=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>
128public 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>
193private 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>