summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java')
-rw-r--r--src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
new file mode 100644
index 0000000..6abfdf9
--- /dev/null
+++ b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2015 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.android.messaging.ui.animation;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewOverlay;
+import android.widget.FrameLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * <p>
+ * Shows a vertical "explode" animation for any view inside a view group (e.g. views inside a
+ * ListView). During the animation, a snapshot is taken for the view to the animated and
+ * presented in a popup window or view overlay on top of the original view group. The background
+ * of the view (a highlight) vertically expands (explodes) during the animation.
+ * </p>
+ * <p>
+ * The exact implementation of the animation depends on platform API level. For JB_MR2 and later,
+ * the implementation utilizes ViewOverlay to perform highly performant overlay animations; for
+ * older API levels, the implementation falls back to using a full screen popup window to stage
+ * the animation.
+ * </p>
+ * <p>
+ * To start this animation, call {@link #startAnimationForView(ViewGroup, View, View, boolean, int)}
+ * </p>
+ */
+public class ViewGroupItemVerticalExplodeAnimation {
+ /**
+ * Starts a vertical explode animation for a given view situated in a given container.
+ *
+ * @param container the container of the view which determines the explode animation's final
+ * size
+ * @param viewToAnimate the view to be animated. The view will be highlighted by the explode
+ * highlight, which expands from the size of the view to the size of the container.
+ * @param animationStagingView the view that stages the animation. Since viewToAnimate may be
+ * removed from the view tree during the animation, we need a view that'll be alive
+ * for the duration of the animation so that the animation won't get cancelled.
+ * @param snapshotView whether a snapshot of the view to animate is needed.
+ */
+ public static void startAnimationForView(final ViewGroup container, final View viewToAnimate,
+ final View animationStagingView, final boolean snapshotView, final int duration) {
+ if (OsUtil.isAtLeastJB_MR2() && (viewToAnimate.getContext() instanceof Activity)) {
+ new ViewExplodeAnimationJellyBeanMR2(viewToAnimate, container, snapshotView, duration)
+ .startAnimation();
+ } else {
+ // Pre JB_MR2, this animation can cause rendering failures which causes the framework
+ // to fall back to software rendering where camera preview isn't supported (b/18264647)
+ // just skip the animation to avoid this case.
+ }
+ }
+
+ /**
+ * Implementation class for API level >= 18.
+ */
+ @TargetApi(18)
+ private static class ViewExplodeAnimationJellyBeanMR2 {
+ private final View mViewToAnimate;
+ private final ViewGroup mContainer;
+ private final View mSnapshot;
+ private final Bitmap mViewBitmap;
+ private final int mDuration;
+
+ public ViewExplodeAnimationJellyBeanMR2(final View viewToAnimate, final ViewGroup container,
+ final boolean snapshotView, final int duration) {
+ mViewToAnimate = viewToAnimate;
+ mContainer = container;
+ mDuration = duration;
+ if (snapshotView) {
+ mViewBitmap = snapshotView(viewToAnimate);
+ mSnapshot = new View(viewToAnimate.getContext());
+ } else {
+ mSnapshot = null;
+ mViewBitmap = null;
+ }
+ }
+
+ public void startAnimation() {
+ final Context context = mViewToAnimate.getContext();
+ final Resources resources = context.getResources();
+ final View decorView = ((Activity) context).getWindow().getDecorView();
+ final ViewOverlay viewOverlay = decorView.getOverlay();
+ if (viewOverlay instanceof ViewGroupOverlay) {
+ final ViewGroupOverlay overlay = (ViewGroupOverlay) viewOverlay;
+
+ // Add a shadow layer to the overlay.
+ final FrameLayout shadowContainerLayer = new FrameLayout(context);
+ final Drawable oldBackground = mViewToAnimate.getBackground();
+ final Rect containerRect = UiUtils.getMeasuredBoundsOnScreen(mContainer);
+ final Rect decorRect = UiUtils.getMeasuredBoundsOnScreen(decorView);
+ // Position the container rect relative to the decor rect since the decor rect
+ // defines whether the view overlay will be positioned.
+ containerRect.offset(-decorRect.left, -decorRect.top);
+ shadowContainerLayer.setLeft(containerRect.left);
+ shadowContainerLayer.setTop(containerRect.top);
+ shadowContainerLayer.setBottom(containerRect.bottom);
+ shadowContainerLayer.setRight(containerRect.right);
+ shadowContainerLayer.setBackgroundColor(resources.getColor(
+ R.color.open_conversation_animation_background_shadow));
+ // Per design request, temporarily clear out the background of the item content
+ // to not show any ripple effects during animation.
+ if (!(oldBackground instanceof ColorDrawable)) {
+ mViewToAnimate.setBackground(null);
+ }
+ overlay.add(shadowContainerLayer);
+
+ // Add a expand layer and position it with in the shadow background, so it can
+ // be properly clipped to the container bounds during the animation.
+ final View expandLayer = new View(context);
+ final int elevation = resources.getDimensionPixelSize(
+ R.dimen.explode_animation_highlight_elevation);
+ final Rect viewRect = UiUtils.getMeasuredBoundsOnScreen(mViewToAnimate);
+ // Frame viewRect from screen space to containerRect space.
+ viewRect.offset(-containerRect.left - decorRect.left,
+ -containerRect.top - decorRect.top);
+ // Since the expand layer expands at the same rate above and below, we need to
+ // compute the expand scale using the bigger of the top/bottom distances.
+ final int expandLayerHalfHeight = viewRect.height() / 2;
+ final int topDist = viewRect.top;
+ final int bottomDist = containerRect.height() - viewRect.bottom;
+ final float scale = expandLayerHalfHeight == 0 ? 1 :
+ ((float) Math.max(topDist, bottomDist) + expandLayerHalfHeight) /
+ expandLayerHalfHeight;
+ // Position the expand layer initially to exactly match the animated item.
+ shadowContainerLayer.addView(expandLayer);
+ expandLayer.setLeft(viewRect.left);
+ expandLayer.setTop(viewRect.top);
+ expandLayer.setBottom(viewRect.bottom);
+ expandLayer.setRight(viewRect.right);
+ expandLayer.setBackgroundColor(resources.getColor(
+ R.color.conversation_background));
+ ViewCompat.setElevation(expandLayer, elevation);
+
+ // Conditionally stage the snapshot in the overlay.
+ if (mSnapshot != null) {
+ shadowContainerLayer.addView(mSnapshot);
+ mSnapshot.setLeft(viewRect.left);
+ mSnapshot.setTop(viewRect.top);
+ mSnapshot.setBottom(viewRect.bottom);
+ mSnapshot.setRight(viewRect.right);
+ mSnapshot.setBackground(new BitmapDrawable(resources, mViewBitmap));
+ ViewCompat.setElevation(mSnapshot, elevation);
+ }
+
+ // Apply a scale animation to scale to full screen.
+ expandLayer.animate().scaleY(scale)
+ .setDuration(mDuration)
+ .setInterpolator(UiUtils.EASE_IN_INTERPOLATOR)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ // Clean up the views added to overlay on animation finish.
+ overlay.remove(shadowContainerLayer);
+ mViewToAnimate.setBackground(oldBackground);
+ if (mViewBitmap != null) {
+ mViewBitmap.recycle();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Take a snapshot of the given review, return a Bitmap object that's owned by the caller.
+ */
+ static Bitmap snapshotView(final View view) {
+ // Save the content of the view into a bitmap.
+ final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(),
+ view.getHeight(), Bitmap.Config.ARGB_8888);
+ // Strip the view of its background when taking a snapshot so that things like touch
+ // feedback don't get accidentally snapshotted.
+ final Drawable viewBackground = view.getBackground();
+ ImageUtils.setBackgroundDrawableOnView(view, null);
+ view.draw(new Canvas(viewBitmap));
+ ImageUtils.setBackgroundDrawableOnView(view, viewBackground);
+ return viewBitmap;
+ }
+}