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