Contact detail transition animation

Change-Id: I0ee47a78940d68092c518a35fbe1c78a2b5323db
diff --git a/src/com/android/contacts/widget/TransitionAnimationView.java b/src/com/android/contacts/widget/TransitionAnimationView.java
new file mode 100644
index 0000000..938ff8a
--- /dev/null
+++ b/src/com/android/contacts/widget/TransitionAnimationView.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010 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.contacts.widget;
+
+import com.android.contacts.R;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorInflater;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+
+/**
+ * A container for a view that needs to have exit/enter animations when rebinding data.
+ * This layout should have a single child.  Just before rebinding data that child
+ * should make this call:
+ * <pre>
+ *   TransitionAnimationView.startAnimation(this);
+ * </pre>
+ */
+public class TransitionAnimationView extends FrameLayout implements AnimatorListener {
+
+    private View mPreviousStateView;
+    private Bitmap mPreviousStateBitmap;
+    private int mEnterAnimationId;
+    private int mExitAnimationId;
+    private int mAnimationDuration;
+    private Rect mClipMargins = new Rect();
+    private Rect mClipRect = new Rect();
+    private Animator mEnterAnimation;
+    private Animator mExitAnimation;
+
+    public TransitionAnimationView(Context context) {
+        this(context, null, 0);
+    }
+
+    public TransitionAnimationView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TransitionAnimationView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = getContext().obtainStyledAttributes(
+                attrs, R.styleable.TransitionAnimationView);
+
+        mEnterAnimationId = a.getResourceId(R.styleable.TransitionAnimationView_enterAnimation,
+                android.R.anim.animator_fade_in);
+        mExitAnimationId = a.getResourceId(R.styleable.TransitionAnimationView_exitAnimation,
+                android.R.anim.animator_fade_out);
+        mClipMargins.left = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginLeft, 0);
+        mClipMargins.top = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginTop, 0);
+        mClipMargins.right = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginRight, 0);
+        mClipMargins.bottom = a.getDimensionPixelOffset(
+                R.styleable.TransitionAnimationView_clipMarginBottom, 0);
+        mAnimationDuration = a.getInt(
+                R.styleable.TransitionAnimationView_animationDuration, 100);
+
+        a.recycle();
+
+        mPreviousStateView = new View(context);
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+        addView(mPreviousStateView);
+
+        mEnterAnimation = AnimatorInflater.loadAnimator(getContext(), mEnterAnimationId);
+        if (mEnterAnimation == null) {
+            throw new IllegalArgumentException("Invalid enter animation: " + mEnterAnimationId);
+        }
+        mEnterAnimation.addListener(this);
+
+        mExitAnimation = AnimatorInflater.loadAnimator(getContext(), mExitAnimationId);
+        if (mExitAnimation == null) {
+            throw new IllegalArgumentException("Invalid exit animation: " + mExitAnimationId);
+        }
+
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (changed) {
+            mPreviousStateBitmap = Bitmap.createBitmap(
+                    right - left, bottom - top, Bitmap.Config.ARGB_8888);
+            mPreviousStateView.setBackgroundDrawable(
+                    new BitmapDrawable(getContext().getResources(), mPreviousStateBitmap));
+            mClipRect.set(mClipMargins.left, mClipMargins.top,
+                    right - left - mClipMargins.right, bottom - top - mClipMargins.bottom);
+        }
+    }
+
+    public static void startAnimation(View view, boolean closing) {
+        TransitionAnimationView container = null;
+        ViewParent parent = view.getParent();
+        while (parent instanceof View) {
+            if (parent instanceof TransitionAnimationView) {
+                container = (TransitionAnimationView) parent;
+                break;
+            }
+            parent = parent.getParent();
+        }
+
+        if (container != null) {
+            container.start(view, closing);
+        }
+    }
+
+    private void start(View view, boolean closing) {
+        if (view.getVisibility() != View.VISIBLE) {
+            if (!closing) {
+                mEnterAnimation.setTarget(view);
+                mEnterAnimation.start();
+            }
+        } else if (closing) {
+            mExitAnimation.setTarget(view);
+            mExitAnimation.start();
+        } else {
+            Canvas canvas = new Canvas(mPreviousStateBitmap);
+            Paint paint = new Paint();
+            paint.setColor(Color.TRANSPARENT);
+            canvas.drawRect(0, 0, mPreviousStateBitmap.getWidth(), mPreviousStateBitmap.getHeight(),
+                    paint);
+            canvas.clipRect(mClipRect);
+            view.draw(canvas);
+            mPreviousStateView.setVisibility(View.VISIBLE);
+
+            mEnterAnimation.setTarget(view);
+            mEnterAnimation.setDuration(mAnimationDuration);
+            mEnterAnimation.start();
+        }
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+        mPreviousStateView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+}