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"> </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 <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"> 85 86 <LinearLayout android:layout_width="match_parent" 87 android:layout_height="wrap_content" 88 android:orientation="vertical" 89 android:padding="16dp"> 90 91 <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" /> 99 100 </LinearLayout> 101 102 <!-- 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 --> 108 109 <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" /> 115 116 </FrameLayout> 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 @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 @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 > (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 @Override 271 public void onAnimationEnd(Animator animation) { 272 mCurrentAnimator = null; 273 } 274 275 @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 @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 @Override 312 public void onAnimationEnd(Animator animation) { 313 thumbView.setAlpha(1f); 314 expandedImageView.setVisibility(View.GONE); 315 mCurrentAnimator = null; 316 } 317 318 @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>