diff --git a/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml b/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml new file mode 100644 index 000000000..eeb53781e --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg new file mode 100644 index 000000000..974581894 Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p1.jpg differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg new file mode 100644 index 000000000..db8731f01 Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p2.jpg differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg new file mode 100644 index 000000000..b240b3a02 Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p3.jpg differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg new file mode 100644 index 000000000..4de929221 Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-nodpi/p4.jpg differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/samples/devbytes/animation/ActivityAnimations/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml b/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml new file mode 100644 index 000000000..c11a568e7 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/res/layout/activity_animations.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml b/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml new file mode 100644 index 000000000..cb2ced229 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/res/layout/picture_info.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml b/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml new file mode 100644 index 000000000..aab540e09 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/res/menu/activity_better_window_animations.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml b/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml new file mode 100644 index 000000000..f9409b551 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + Activity Animations + PictureInfo! + Slow + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml b/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml new file mode 100644 index 000000000..9d8334244 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/res/values/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java new file mode 100644 index 000000000..4a3e0d95b --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ActivityAnimations.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.activityanim; + +import java.util.ArrayList; +import java.util.HashMap; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.GridLayout; +import android.widget.ImageView; + +/** + * This example shows how to create a custom activity animation when you want something more + * than window animations can provide. The idea is to disable window animations for the + * activities and to instead launch or return from the sub-activity immediately, but use + * property animations inside the activities to customize the transition. + * + * Watch the associated video for this demo on the DevBytes channel of developer.android.com + * or on the DevBytes playlist in the androiddevelopers channel on YouTube at + * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0. + */ +public class ActivityAnimations extends Activity { + + private static final String PACKAGE = "com.example.android.activityanim"; + static float sAnimatorScale = 1; + + GridLayout mGridLayout; + HashMap mPicturesData = new HashMap(); + BitmapUtils mBitmapUtils = new BitmapUtils(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_animations); + + // Grayscale filter used on all thumbnails + ColorMatrix grayMatrix = new ColorMatrix(); + grayMatrix.setSaturation(0); + ColorMatrixColorFilter grayscaleFilter = new ColorMatrixColorFilter(grayMatrix); + + mGridLayout = (GridLayout) findViewById(R.id.gridLayout); + mGridLayout.setColumnCount(3); + mGridLayout.setUseDefaultMargins(true); + + // add all photo thumbnails to layout + Resources resources = getResources(); + ArrayList pictures = mBitmapUtils.loadPhotos(resources); + for (int i = 0; i < pictures.size(); ++i) { + PictureData pictureData = pictures.get(i); + BitmapDrawable thumbnailDrawable = + new BitmapDrawable(resources, pictureData.thumbnail); + thumbnailDrawable.setColorFilter(grayscaleFilter); + ImageView imageView = new ImageView(this); + imageView.setOnClickListener(thumbnailClickListener); + imageView.setImageDrawable(thumbnailDrawable); + mPicturesData.put(imageView, pictureData); + mGridLayout.addView(imageView); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_better_window_animations, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_slow) { + sAnimatorScale = item.isChecked() ? 1 : 5; + item.setChecked(!item.isChecked()); + } + return super.onOptionsItemSelected(item); + } + + /** + * When the user clicks a thumbnail, bundle up information about it and launch the + * details activity. + */ + private View.OnClickListener thumbnailClickListener = new View.OnClickListener() { + + @Override + public void onClick(View v) { + // Interesting data to pass across are the thumbnail size/location, the + // resourceId of the source bitmap, the picture description, and the + // orientation (to avoid returning back to an obsolete configuration if + // the device rotates again in the meantime) + int[] screenLocation = new int[2]; + v.getLocationOnScreen(screenLocation); + PictureData info = mPicturesData.get(v); + Intent subActivity = new Intent(ActivityAnimations.this, + PictureDetailsActivity.class); + int orientation = getResources().getConfiguration().orientation; + subActivity. + putExtra(PACKAGE + ".orientation", orientation). + putExtra(PACKAGE + ".resourceId", info.resourceId). + putExtra(PACKAGE + ".left", screenLocation[0]). + putExtra(PACKAGE + ".top", screenLocation[1]). + putExtra(PACKAGE + ".width", v.getWidth()). + putExtra(PACKAGE + ".height", v.getHeight()). + putExtra(PACKAGE + ".description", info.description); + startActivity(subActivity); + + // Override transitions: we don't want the normal window animation in addition + // to our custom one + overridePendingTransition(0, 0); + } + }; + +} diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java new file mode 100644 index 000000000..a8034dce4 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/BitmapUtils.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.activityanim; + +import java.util.ArrayList; +import java.util.HashMap; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.widget.ImageView; + +public class BitmapUtils { + + int[] mPhotos = { + R.drawable.p1, + R.drawable.p2, + R.drawable.p3, + R.drawable.p4 + }; + + String[] mDescriptions = { + "This picture was taken while sunbathing in a natural hot spring, which was " + + "unfortunately filled with acid, which is a lasting memory from that trip, whenever I " + + "I look at my own skin.", + "I took this shot with a pinhole camera mounted on a tripod constructed out of " + + "soda straws. I felt that that combination best captured the beauty of the landscape " + + "in juxtaposition with the detritus of mankind.", + "I don't remember where or when I took this picture. All I know is that I was really " + + "drunk at the time, and I woke up without my left sock.", + "Right before I took this picture, there was a busload of school children right " + + "in my way. I knew the perfect shot was coming, so I quickly yelled 'Free candy!!!' " + + "and they scattered.", + }; + + static HashMap sBitmapResourceMap = new HashMap(); + + /** + * Load pictures and descriptions. A real app wouldn't do it this way, but that's + * not the point of this animation demo. Loading asynchronously is a better way to go + * for what can be time-consuming operations. + */ + public ArrayList loadPhotos(Resources resources) { + ArrayList pictures = new ArrayList(); + for (int i = 0; i < 30; ++i) { + int resourceId = mPhotos[(int) (Math.random() * mPhotos.length)]; + Bitmap bitmap = getBitmap(resources, resourceId); + Bitmap thumbnail = getThumbnail(bitmap, 200); + String description = mDescriptions[(int) (Math.random() * mDescriptions.length)]; + pictures.add(new PictureData(resourceId, description, thumbnail)); + } + return pictures; + } + + /** + * Utility method to get bitmap from cache or, if not there, load it + * from its resource. + */ + static Bitmap getBitmap(Resources resources, int resourceId) { + Bitmap bitmap = sBitmapResourceMap.get(resourceId); + if (bitmap == null) { + bitmap = BitmapFactory.decodeResource(resources, resourceId); + sBitmapResourceMap.put(resourceId, bitmap); + } + return bitmap; + } + + /** + * Create and return a thumbnail image given the original source bitmap and a max + * dimension (width or height). + */ + private Bitmap getThumbnail(Bitmap original, int maxDimension) { + int width = original.getWidth(); + int height = original.getHeight(); + int scaledWidth, scaledHeight; + if (width >= height) { + float scaleFactor = (float) maxDimension / width; + scaledWidth = 200; + scaledHeight = (int) (scaleFactor * height); + } else { + float scaleFactor = (float) maxDimension / height; + scaledWidth = (int) (scaleFactor * width); + scaledHeight = 200; + } + Bitmap thumbnail = Bitmap.createScaledBitmap(original, scaledWidth, scaledHeight, true); + + return thumbnail; + } + + +} diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java new file mode 100644 index 000000000..eb2d1264a --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureData.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.activityanim; + +import android.graphics.Bitmap; + +public class PictureData { + int resourceId; + String description; + Bitmap thumbnail; + + public PictureData(int resourceId, String description, Bitmap thumbnail) { + this.resourceId = resourceId; + this.description = description; + this.thumbnail = thumbnail; + } +} diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java new file mode 100644 index 000000000..e1674c91e --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/PictureDetailsActivity.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.activityanim; + +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * This sub-activity shows a zoomed-in view of a specific photo, along with the + * picture's text description. Most of the logic is for the animations that will + * be run when the activity is being launched and exited. When launching, + * the large version of the picture will resize from the thumbnail version in the + * main activity, colorizing it from the thumbnail's grayscale version at the + * same time. Meanwhile, the black background of the activity will fade in and + * the description will eventually slide into place. The exit animation runs all + * of this in reverse. + * + */ +public class PictureDetailsActivity extends Activity { + + private static final TimeInterpolator sDecelerator = new DecelerateInterpolator(); + private static final TimeInterpolator sAccelerator = new AccelerateInterpolator(); + private static final String PACKAGE_NAME = "com.example.android.activityanim"; + private static final int ANIM_DURATION = 500; + + private BitmapDrawable mBitmapDrawable; + private ColorMatrix colorizerMatrix = new ColorMatrix(); + ColorDrawable mBackground; + int mLeftDelta; + int mTopDelta; + float mWidthScale; + float mHeightScale; + private ImageView mImageView; + private TextView mTextView; + private FrameLayout mTopLevelLayout; + private ShadowLayout mShadowLayout; + private int mOriginalOrientation; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.picture_info); + mImageView = (ImageView) findViewById(R.id.imageView); + mTopLevelLayout = (FrameLayout) findViewById(R.id.topLevelLayout); + mShadowLayout = (ShadowLayout) findViewById(R.id.shadowLayout); + mTextView = (TextView) findViewById(R.id.description); + + // Retrieve the data we need for the picture/description to display and + // the thumbnail to animate it from + Bundle bundle = getIntent().getExtras(); + Bitmap bitmap = BitmapUtils.getBitmap(getResources(), + bundle.getInt(PACKAGE_NAME + ".resourceId")); + String description = bundle.getString(PACKAGE_NAME + ".description"); + final int thumbnailTop = bundle.getInt(PACKAGE_NAME + ".top"); + final int thumbnailLeft = bundle.getInt(PACKAGE_NAME + ".left"); + final int thumbnailWidth = bundle.getInt(PACKAGE_NAME + ".width"); + final int thumbnailHeight = bundle.getInt(PACKAGE_NAME + ".height"); + mOriginalOrientation = bundle.getInt(PACKAGE_NAME + ".orientation"); + + mBitmapDrawable = new BitmapDrawable(getResources(), bitmap); + mImageView.setImageDrawable(mBitmapDrawable); + mTextView.setText(description); + + mBackground = new ColorDrawable(Color.BLACK); + mTopLevelLayout.setBackground(mBackground); + + // Only run the animation if we're coming from the parent activity, not if + // we're recreated automatically by the window manager (e.g., device rotation) + if (savedInstanceState == null) { + ViewTreeObserver observer = mImageView.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + + @Override + public boolean onPreDraw() { + mImageView.getViewTreeObserver().removeOnPreDrawListener(this); + + // Figure out where the thumbnail and full size versions are, relative + // to the screen and each other + int[] screenLocation = new int[2]; + mImageView.getLocationOnScreen(screenLocation); + mLeftDelta = thumbnailLeft - screenLocation[0]; + mTopDelta = thumbnailTop - screenLocation[1]; + + // Scale factors to make the large version the same size as the thumbnail + mWidthScale = (float) thumbnailWidth / mImageView.getWidth(); + mHeightScale = (float) thumbnailHeight / mImageView.getHeight(); + + runEnterAnimation(); + + return true; + } + }); + } + } + + /** + * The enter animation scales the picture in from its previous thumbnail + * size/location, colorizing it in parallel. In parallel, the background of the + * activity is fading in. When the pictue is in place, the text description + * drops down. + */ + public void runEnterAnimation() { + final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale); + + // Set starting values for properties we're going to animate. These + // values scale and position the full size version down to the thumbnail + // size/location, from which we'll animate it back up + mImageView.setPivotX(0); + mImageView.setPivotY(0); + mImageView.setScaleX(mWidthScale); + mImageView.setScaleY(mHeightScale); + mImageView.setTranslationX(mLeftDelta); + mImageView.setTranslationY(mTopDelta); + + // We'll fade the text in later + mTextView.setAlpha(0); + + // Animate scale and translation to go from thumbnail to full size + mImageView.animate().setDuration(duration). + scaleX(1).scaleY(1). + translationX(0).translationY(0). + setInterpolator(sDecelerator). + withEndAction(new Runnable() { + public void run() { + // Animate the description in after the image animation + // is done. Slide and fade the text in from underneath + // the picture. + mTextView.setTranslationY(-mTextView.getHeight()); + mTextView.animate().setDuration(duration/2). + translationY(0).alpha(1). + setInterpolator(sDecelerator); + } + }); + + // Fade in the black background + ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255); + bgAnim.setDuration(duration); + bgAnim.start(); + + // Animate a color filter to take the image from grayscale to full color. + // This happens in parallel with the image scaling and moving into place. + ObjectAnimator colorizer = ObjectAnimator.ofFloat(PictureDetailsActivity.this, + "saturation", 0, 1); + colorizer.setDuration(duration); + colorizer.start(); + + // Animate a drop-shadow of the image + ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout, "shadowDepth", 0, 1); + shadowAnim.setDuration(duration); + shadowAnim.start(); + } + + /** + * The exit animation is basically a reverse of the enter animation, except that if + * the orientation has changed we simply scale the picture back into the center of + * the screen. + * + * @param endAction This action gets run after the animation completes (this is + * when we actually switch activities) + */ + public void runExitAnimation(final Runnable endAction) { + final long duration = (long) (ANIM_DURATION * ActivityAnimations.sAnimatorScale); + + // No need to set initial values for the reverse animation; the image is at the + // starting size/location that we want to start from. Just animate to the + // thumbnail size/location that we retrieved earlier + + // Caveat: configuration change invalidates thumbnail positions; just animate + // the scale around the center. Also, fade it out since it won't match up with + // whatever's actually in the center + final boolean fadeOut; + if (getResources().getConfiguration().orientation != mOriginalOrientation) { + mImageView.setPivotX(mImageView.getWidth() / 2); + mImageView.setPivotY(mImageView.getHeight() / 2); + mLeftDelta = 0; + mTopDelta = 0; + fadeOut = true; + } else { + fadeOut = false; + } + + // First, slide/fade text out of the way + mTextView.animate().translationY(-mTextView.getHeight()).alpha(0). + setDuration(duration/2).setInterpolator(sAccelerator). + withEndAction(new Runnable() { + public void run() { + // Animate image back to thumbnail size/location + mImageView.animate().setDuration(duration). + scaleX(mWidthScale).scaleY(mHeightScale). + translationX(mLeftDelta).translationY(mTopDelta). + withEndAction(endAction); + if (fadeOut) { + mImageView.animate().alpha(0); + } + // Fade out background + ObjectAnimator bgAnim = ObjectAnimator.ofInt(mBackground, "alpha", 0); + bgAnim.setDuration(duration); + bgAnim.start(); + + // Animate the shadow of the image + ObjectAnimator shadowAnim = ObjectAnimator.ofFloat(mShadowLayout, + "shadowDepth", 1, 0); + shadowAnim.setDuration(duration); + shadowAnim.start(); + + // Animate a color filter to take the image back to grayscale, + // in parallel with the image scaling and moving into place. + ObjectAnimator colorizer = + ObjectAnimator.ofFloat(PictureDetailsActivity.this, + "saturation", 1, 0); + colorizer.setDuration(duration); + colorizer.start(); + } + }); + + + } + + /** + * Overriding this method allows us to run our exit animation first, then exiting + * the activity when it is complete. + */ + @Override + public void onBackPressed() { + runExitAnimation(new Runnable() { + public void run() { + // *Now* go ahead and exit the activity + finish(); + } + }); + } + + /** + * This is called by the colorizing animator. It sets a saturation factor that is then + * passed onto a filter on the picture's drawable. + * @param value + */ + public void setSaturation(float value) { + colorizerMatrix.setSaturation(value); + ColorMatrixColorFilter colorizerFilter = new ColorMatrixColorFilter(colorizerMatrix); + mBitmapDrawable.setColorFilter(colorizerFilter); + } + + @Override + public void finish() { + super.finish(); + + // override transitions to skip the standard window animations + overridePendingTransition(0, 0); + } +} diff --git a/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java new file mode 100644 index 000000000..b3bc96180 --- /dev/null +++ b/samples/devbytes/animation/ActivityAnimations/src/com/example/android/activityanim/ShadowLayout.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.activityanim; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.BlurMaskFilter.Blur; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +/** + * This custom layout paints a drop shadow behind all children. The size and opacity + * of the drop shadow is determined by a "depth" factor that can be set and animated. + */ +public class ShadowLayout extends RelativeLayout { + + Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + float mShadowDepth; + Bitmap mShadowBitmap; + static final int BLUR_RADIUS = 6; + static final RectF sShadowRectF = new RectF(0, 0, 200, 200); + static final Rect sShadowRect = new Rect(0, 0, 200 + 2 * BLUR_RADIUS, 200 + 2 * BLUR_RADIUS); + static RectF tempShadowRectF = new RectF(0, 0, 0, 0); + + public ShadowLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public ShadowLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ShadowLayout(Context context) { + super(context); + init(); + } + + /** + * Called by the constructors - sets up the drawing parameters for the drop shadow. + */ + private void init() { + mShadowPaint.setColor(Color.BLACK); + mShadowPaint.setStyle(Style.FILL); + setWillNotDraw(false); + mShadowBitmap = Bitmap.createBitmap(sShadowRect.width(), + sShadowRect.height(), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(mShadowBitmap); + mShadowPaint.setMaskFilter(new BlurMaskFilter(BLUR_RADIUS, Blur.NORMAL)); + c.translate(BLUR_RADIUS, BLUR_RADIUS); + c.drawRoundRect(sShadowRectF, sShadowRectF.width() / 40, + sShadowRectF.height() / 40, mShadowPaint); + } + + /** + * The "depth" factor determines the offset distance and opacity of the shadow (shadows that + * are further away from the source are offset greater and are more translucent). + * @param depth + */ + public void setShadowDepth(float depth) { + if (depth != mShadowDepth) { + mShadowDepth = depth; + mShadowPaint.setAlpha((int) (100 + 150 * (1 - mShadowDepth))); + invalidate(); // We need to redraw when the shadow attributes change + } + } + + /** + * Overriding onDraw allows us to draw shadows behind every child of this container. + * onDraw() is called to draw a layout's content before the children are drawn, so the + * shadows will be drawn first, behind the children (which is what we want). + */ + @Override + protected void onDraw(Canvas canvas) { + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (child.getVisibility() != View.VISIBLE || child.getAlpha() == 0) { + continue; + } + int depthFactor = (int) (80 * mShadowDepth); + canvas.save(); + canvas.translate(child.getLeft() + depthFactor, + child.getTop() + depthFactor); + canvas.concat(child.getMatrix()); + tempShadowRectF.right = child.getWidth(); + tempShadowRectF.bottom = child.getHeight(); + canvas.drawBitmap(mShadowBitmap, sShadowRect, tempShadowRectF, mShadowPaint); + canvas.restore(); + } + } + + +} diff --git a/samples/devbytes/animation/Anticipation/AndroidManifest.xml b/samples/devbytes/animation/Anticipation/AndroidManifest.xml new file mode 100644 index 000000000..c9415245f --- /dev/null +++ b/samples/devbytes/animation/Anticipation/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/samples/devbytes/animation/Anticipation/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/samples/devbytes/animation/Anticipation/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/samples/devbytes/animation/Anticipation/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/Anticipation/res/layout/main.xml b/samples/devbytes/animation/Anticipation/res/layout/main.xml new file mode 100644 index 000000000..7da093f48 --- /dev/null +++ b/samples/devbytes/animation/Anticipation/res/layout/main.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml b/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml new file mode 100644 index 000000000..6e9521a16 --- /dev/null +++ b/samples/devbytes/animation/Anticipation/res/values-v14/styles.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/Anticipation/res/values/strings.xml b/samples/devbytes/animation/Anticipation/res/values/strings.xml new file mode 100644 index 000000000..90976294b --- /dev/null +++ b/samples/devbytes/animation/Anticipation/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + Anticipation + Hello world! + Settings + + \ No newline at end of file diff --git a/samples/devbytes/animation/Anticipation/res/values/styles.xml b/samples/devbytes/animation/Anticipation/res/values/styles.xml new file mode 100644 index 000000000..27658b7d6 --- /dev/null +++ b/samples/devbytes/animation/Anticipation/res/values/styles.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java new file mode 100644 index 000000000..707765bb9 --- /dev/null +++ b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/AnticiButton.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.anticipation; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.Button; + +/** + * Custom button which can be deformed by skewing the top left and right, to simulate + * anticipation and follow-through animation effects. Clicking on the button runs + * an animation which moves the button left or right, applying the skew effect to the + * button. The logic of drawing the button with a skew transform is handled in the + * draw() override. + */ +public class AnticiButton extends Button { + + private static final LinearInterpolator sLinearInterpolator = new LinearInterpolator(); + private static final DecelerateInterpolator sDecelerator = new DecelerateInterpolator(8); + private static final AccelerateInterpolator sAccelerator = new AccelerateInterpolator(); + private static final OvershootInterpolator sOvershooter = new OvershootInterpolator(); + private static final DecelerateInterpolator sQuickDecelerator = new DecelerateInterpolator(); + + private float mSkewX = 0; + ObjectAnimator downAnim = null; + boolean mOnLeft = true; + RectF mTempRect = new RectF(); + + public AnticiButton(Context context) { + super(context); + init(); + } + + public AnticiButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public AnticiButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + setOnTouchListener(mTouchListener); + setOnClickListener(new OnClickListener() { + public void onClick(View v) { + runClickAnim(); + } + }); + } + + /** + * The skew effect is handled by changing the transform of the Canvas + * and then calling the usual superclass draw() method. + */ + @Override + public void draw(Canvas canvas) { + if (mSkewX != 0) { + canvas.translate(0, getHeight()); + canvas.skew(mSkewX, 0); + canvas.translate(0, -getHeight()); + } + super.draw(canvas); + } + + /** + * Anticipate the future animation by rearing back, away from the direction of travel + */ + private void runPressAnim() { + downAnim = ObjectAnimator.ofFloat(this, "skewX", mOnLeft ? .5f : -.5f); + downAnim.setDuration(2500); + downAnim.setInterpolator(sDecelerator); + downAnim.start(); + } + + /** + * Finish the "anticipation" animation (skew the button back from the direction of + * travel), animate it to the other side of the screen, then un-skew the button + * with an Overshoot effect. + */ + private void runClickAnim() { + // Anticipation + ObjectAnimator finishDownAnim = null; + if (downAnim != null && downAnim.isRunning()) { + // finish the skew animation quickly + downAnim.cancel(); + finishDownAnim = ObjectAnimator.ofFloat(this, "skewX", + mOnLeft ? .5f : -.5f); + finishDownAnim.setDuration(150); + finishDownAnim.setInterpolator(sQuickDecelerator); + } + + // Slide. Use LinearInterpolator in this rare situation where we want to start + // and end fast (no acceleration or deceleration, since we're doing that part + // during the anticipation and overshoot phases). + ObjectAnimator moveAnim = ObjectAnimator.ofFloat(this, + View.TRANSLATION_X, mOnLeft ? 400 : 0); + moveAnim.setInterpolator(sLinearInterpolator); + moveAnim.setDuration(150); + + // Then overshoot by stopping the movement but skewing the button as if it couldn't + // all stop at once + ObjectAnimator skewAnim = ObjectAnimator.ofFloat(this, "skewX", + mOnLeft ? -.5f : .5f); + skewAnim.setInterpolator(sQuickDecelerator); + skewAnim.setDuration(100); + // and wobble it + ObjectAnimator wobbleAnim = ObjectAnimator.ofFloat(this, "skewX", 0); + wobbleAnim.setInterpolator(sOvershooter); + wobbleAnim.setDuration(150); + AnimatorSet set = new AnimatorSet(); + set.playSequentially(moveAnim, skewAnim, wobbleAnim); + if (finishDownAnim != null) { + set.play(finishDownAnim).before(moveAnim); + } + set.start(); + mOnLeft = !mOnLeft; + } + + /** + * Restore the button to its un-pressed state + */ + private void runCancelAnim() { + if (downAnim != null && downAnim.isRunning()) { + downAnim.cancel(); + ObjectAnimator reverser = ObjectAnimator.ofFloat(this, "skewX", 0); + reverser.setDuration(200); + reverser.setInterpolator(sAccelerator); + reverser.start(); + downAnim = null; + } + } + + /** + * Handle touch events directly since we want to react on down/up events, not just + * button clicks + */ + private View.OnTouchListener mTouchListener = new View.OnTouchListener() { + + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + if (isPressed()) { + performClick(); + setPressed(false); + break; + } + // No click: Fall through; equivalent to cancel event + case MotionEvent.ACTION_CANCEL: + // Run the cancel animation in either case + runCancelAnim(); + break; + case MotionEvent.ACTION_MOVE: + float x = event.getX(); + float y = event.getY(); + boolean isInside = (x > 0 && x < getWidth() && + y > 0 && y < getHeight()); + if (isPressed() != isInside) { + setPressed(isInside); + } + break; + case MotionEvent.ACTION_DOWN: + setPressed(true); + runPressAnim(); + break; + default: + break; + } + return true; + } + }; + + public float getSkewX() { + return mSkewX; + } + + /** + * Sets the amount of left/right skew on the button, which determines how far the button + * leans. + */ + public void setSkewX(float value) { + if (value != mSkewX) { + mSkewX = value; + invalidate(); // force button to redraw with new skew value + invalidateSkewedBounds(); // also invalidate appropriate area of parent + } + } + + /** + * Need to invalidate proper area of parent for skewed bounds + */ + private void invalidateSkewedBounds() { + if (mSkewX != 0) { + Matrix matrix = new Matrix(); + matrix.setSkew(-mSkewX, 0); + mTempRect.set(0, 0, getRight(), getBottom()); + matrix.mapRect(mTempRect); + mTempRect.offset(getLeft() + getTranslationX(), getTop() + getTranslationY()); + ((View) getParent()).invalidate((int) mTempRect.left, (int) mTempRect.top, + (int) (mTempRect.right +.5f), (int) (mTempRect.bottom + .5f)); + } + } +} diff --git a/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java new file mode 100644 index 000000000..4ef8f62db --- /dev/null +++ b/samples/devbytes/animation/Anticipation/src/com/example/android/anticipation/Anticipation.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.anticipation; + +import android.app.Activity; +import android.os.Bundle; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; + +/** + * This example shows how to animate some simple deformations on a standard UI widget + * to achieve some interactive and cartoon-ish effects. + * + * Watch the associated video for this demo on the DevBytes channel of developer.android.com + * or on the DevBytes playlist in the androiddevelopers channel on YouTube at + * https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0. + */ +public class Anticipation extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + +} diff --git a/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml b/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml new file mode 100644 index 000000000..195faae23 --- /dev/null +++ b/samples/devbytes/animation/CurvedMotion/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/samples/devbytes/animation/CurvedMotion/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/samples/devbytes/animation/CurvedMotion/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/samples/devbytes/animation/CurvedMotion/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml b/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml new file mode 100644 index 000000000..10adea314 --- /dev/null +++ b/samples/devbytes/animation/CurvedMotion/res/layout/activity_curved_motion.xml @@ -0,0 +1,33 @@ + + + +