OSDN Git Service

Update the popup reveal animation to more closely match window reveal
authorAlan Viverette <alanv@google.com>
Thu, 16 Apr 2015 20:27:50 +0000 (13:27 -0700)
committerAlan Viverette <alanv@google.com>
Thu, 16 Apr 2015 20:27:50 +0000 (13:27 -0700)
Hand-waves the default interpolators for efficiency's sake until we can
implement interpolator caching or preloading.

Change-Id: Ibc618a0c092b08a33fb91265ec15665c94831b6b

api/current.txt
api/system-current.txt
core/java/android/view/View.java
core/java/android/widget/PopupWindow.java
core/java/com/android/internal/transition/EpicenterClipReveal.java
core/java/com/android/internal/transition/EpicenterTranslate.java [new file with mode: 0644]
core/java/com/android/internal/transition/TransitionConstants.java [new file with mode: 0644]
core/res/res/transition/popup_window_enter.xml
core/res/res/transition/popup_window_exit.xml
core/res/res/values/attrs.xml

index b6a630b..465bbbb 100644 (file)
@@ -34701,6 +34701,7 @@ package android.view {
     method protected int getBottomPaddingOffset();
     method public float getCameraDistance();
     method public android.graphics.Rect getClipBounds();
+    method public boolean getClipBounds(android.graphics.Rect);
     method public final boolean getClipToOutline();
     method public java.lang.CharSequence getContentDescription();
     method public final android.content.Context getContext();
index 318d1db..4d16f66 100644 (file)
@@ -37250,6 +37250,7 @@ package android.view {
     method protected int getBottomPaddingOffset();
     method public float getCameraDistance();
     method public android.graphics.Rect getClipBounds();
+    method public boolean getClipBounds(android.graphics.Rect);
     method public final boolean getClipToOutline();
     method public java.lang.CharSequence getContentDescription();
     method public final android.content.Context getContext();
index b6f1e3b..b1ef50c 100644 (file)
@@ -15017,6 +15017,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         return (mClipBounds != null) ? new Rect(mClipBounds) : null;
     }
 
+
+    /**
+     * Populates an output rectangle with the clip bounds of the view,
+     * returning {@code true} if successful or {@code false} if the view's
+     * clip bounds are {@code null}.
+     *
+     * @param outRect rectangle in which to place the clip bounds of the view
+     * @return {@code true} if successful or {@code false} if the view's
+     *         clip bounds are {@code null}
+     */
+    public boolean getClipBounds(Rect outRect) {
+        if (mClipBounds != null) {
+            outRect.set(mClipBounds);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
      * case of an active Animation being run on the view.
index 8792323..03a17ea 100644 (file)
@@ -94,7 +94,6 @@ public class PopupWindow {
     private final int[] mDrawingLocation = new int[2];
     private final int[] mScreenLocation = new int[2];
     private final Rect mTempRect = new Rect();
-    private final Rect mAnchorBounds = new Rect();
 
     private Context mContext;
     private WindowManager mWindowManager;
@@ -159,28 +158,6 @@ public class PopupWindow {
 
     private WeakReference<View> mAnchor;
 
-    private final EpicenterCallback mEpicenterCallback = new EpicenterCallback() {
-        @Override
-        public Rect onGetEpicenter(Transition transition) {
-            final View anchor = mAnchor != null ? mAnchor.get() : null;
-            final View decor = mDecorView;
-            if (anchor == null || decor == null) {
-                return null;
-            }
-
-            final Rect anchorBounds = mAnchorBounds;
-            final int[] anchorLocation = anchor.getLocationOnScreen();
-            final int[] popupLocation = mDecorView.getLocationOnScreen();
-
-            // Compute the position of the anchor relative to the popup.
-            anchorBounds.set(0, 0, anchor.getWidth(), anchor.getHeight());
-            anchorBounds.offset(anchorLocation[0] - popupLocation[0],
-                    anchorLocation[1] - popupLocation[1]);
-
-            return anchorBounds;
-        }
-    };
-
     private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
         @Override
         public void onScrollChanged() {
@@ -355,18 +332,10 @@ public class PopupWindow {
 
     public void setEnterTransition(Transition enterTransition) {
         mEnterTransition = enterTransition;
-
-        if (mEnterTransition != null) {
-            mEnterTransition.setEpicenterCallback(mEpicenterCallback);
-        }
     }
 
     public void setExitTransition(Transition exitTransition) {
         mExitTransition = exitTransition;
-
-        if (mExitTransition != null) {
-            mExitTransition.setEpicenterCallback(mEpicenterCallback);
-        }
     }
 
     private Transition getTransition(int resId) {
@@ -1278,11 +1247,14 @@ public class PopupWindow {
 
         final PopupDecorView decorView = mDecorView;
         decorView.setFitsSystemWindows(mLayoutInsetDecor);
-        decorView.requestEnterTransition(mEnterTransition);
 
         setLayoutDirectionFromAnchor();
 
         mWindowManager.addView(decorView, p);
+
+        if (mEnterTransition != null) {
+            decorView.requestEnterTransition(mEnterTransition);
+        }
     }
 
     private void setLayoutDirectionFromAnchor() {
@@ -1604,13 +1576,25 @@ public class PopupWindow {
         // Ensure any ongoing or pending transitions are canceled.
         decorView.cancelTransitions();
 
-        unregisterForScrollChanged();
-
         mIsShowing = false;
         mIsTransitioningToDismiss = true;
 
-        if (mExitTransition != null && decorView.isLaidOut()) {
-            decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
+        final Transition exitTransition = mExitTransition;
+        if (exitTransition != null && decorView.isLaidOut()) {
+            // The decor view is non-interactive during exit transitions.
+            final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
+            p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
+            p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+            mWindowManager.updateViewLayout(decorView, p);
+
+            final Rect epicenter = getRelativeAnchorBounds();
+            exitTransition.setEpicenterCallback(new EpicenterCallback() {
+                @Override
+                public Rect onGetEpicenter(Transition transition) {
+                    return epicenter;
+                }
+            });
+            decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
                     dismissImmediate(decorView, contentHolder, contentView);
@@ -1620,11 +1604,30 @@ public class PopupWindow {
             dismissImmediate(decorView, contentHolder, contentView);
         }
 
+        // Clears the anchor view.
+        unregisterForScrollChanged();
+
         if (mOnDismissListener != null) {
             mOnDismissListener.onDismiss();
         }
     }
 
+    private Rect getRelativeAnchorBounds() {
+        final View anchor = mAnchor != null ? mAnchor.get() : null;
+        final View decor = mDecorView;
+        if (anchor == null || decor == null) {
+            return null;
+        }
+
+        final int[] anchorLocation = anchor.getLocationOnScreen();
+        final int[] popupLocation = mDecorView.getLocationOnScreen();
+
+        // Compute the position of the anchor relative to the popup.
+        final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
+        bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
+        return bounds;
+    }
+
     /**
      * Removes the popup from the window manager and tears down the supporting
      * view hierarchy, if necessary.
@@ -1993,6 +1996,13 @@ public class PopupWindow {
                             observer.removeOnGlobalLayoutListener(this);
                         }
 
+                        final Rect epicenter = getRelativeAnchorBounds();
+                        enterTransition.setEpicenterCallback(new EpicenterCallback() {
+                            @Override
+                            public Rect onGetEpicenter(Transition transition) {
+                                return epicenter;
+                            }
+                        });
                         startEnterTransition(enterTransition);
                     }
                 });
index abb50c1..1a6736a 100644 (file)
@@ -17,15 +17,23 @@ package com.android.internal.transition;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.RectEvaluator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.transition.TransitionValues;
 import android.transition.Visibility;
 import android.util.AttributeSet;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
 
 /**
  * EpicenterClipReveal captures the {@link View#getClipBounds()} before and
@@ -36,10 +44,39 @@ public class EpicenterClipReveal extends Visibility {
     private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
     private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
 
-    public EpicenterClipReveal() {}
+    private final TimeInterpolator mInterpolatorX;
+    private final TimeInterpolator mInterpolatorY;
+    private final boolean mCenterClipBounds;
+
+    public EpicenterClipReveal() {
+        mInterpolatorX = null;
+        mInterpolatorY = null;
+        mCenterClipBounds = false;
+    }
 
     public EpicenterClipReveal(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.EpicenterClipReveal, 0, 0);
+
+        mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false);
+
+        final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0);
+        if (interpolatorX != 0) {
+            mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
+        } else {
+            mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN;
+        }
+
+        final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0);
+        if (interpolatorY != 0) {
+            mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
+        } else {
+            mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
+        }
+
+        a.recycle();
     }
 
     @Override
@@ -82,7 +119,7 @@ public class EpicenterClipReveal extends Visibility {
         // Prepare the view.
         view.setClipBounds(start);
 
-        return createRectAnimator(view, start, end, endValues);
+        return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
     }
 
     @Override
@@ -98,17 +135,23 @@ public class EpicenterClipReveal extends Visibility {
         // Prepare the view.
         view.setClipBounds(start);
 
-        return createRectAnimator(view, start, end, endValues);
+        return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY);
     }
 
     private Rect getEpicenterOrCenter(Rect bestRect) {
         final Rect epicenter = getEpicenter();
         if (epicenter != null) {
+            // Translate the clip bounds to be centered within the target bounds.
+            if (mCenterClipBounds) {
+                final int offsetX = bestRect.centerX() - epicenter.centerX();
+                final int offsetY = bestRect.centerY() - epicenter.centerY();
+                epicenter.offset(offsetX, offsetY);
+            }
             return epicenter;
         }
 
-        int centerX = bestRect.centerX();
-        int centerY = bestRect.centerY();
+        final int centerX = bestRect.centerX();
+        final int centerY = bestRect.centerY();
         return new Rect(centerX, centerY, centerX, centerY);
     }
 
@@ -120,17 +163,71 @@ public class EpicenterClipReveal extends Visibility {
         return clipRect;
     }
 
-    private Animator createRectAnimator(final View view, Rect start, Rect end,
-            TransitionValues endValues) {
-        final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+    private static Animator createRectAnimator(final View view, Rect start, Rect end,
+            TransitionValues endValues, TimeInterpolator interpolatorX,
+            TimeInterpolator interpolatorY) {
         final RectEvaluator evaluator = new RectEvaluator(new Rect());
-        ObjectAnimator anim = ObjectAnimator.ofObject(view, "clipBounds", evaluator, start, end);
-        anim.addListener(new AnimatorListenerAdapter() {
+        final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+
+        final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X);
+        final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end);
+        if (interpolatorX != null) {
+            animX.setInterpolator(interpolatorX);
+        }
+
+        final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y);
+        final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end);
+        if (interpolatorY != null) {
+            animY.setInterpolator(interpolatorY);
+        }
+
+        final AnimatorSet animSet = new AnimatorSet();
+        animSet.playTogether(animX, animY);
+        animSet.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 view.setClipBounds(terminalClip);
             }
         });
-        return anim;
+        return animSet;
+    }
+
+    private static class ClipDimenProperty extends Property<View, Rect> {
+        public static final char TARGET_X = 'x';
+        public static final char TARGET_Y = 'y';
+
+        private final Rect mTempRect = new Rect();
+
+        private final int mTargetDimension;
+
+        public ClipDimenProperty(char targetDimension) {
+            super(Rect.class, "clip_bounds_" + targetDimension);
+
+            mTargetDimension = targetDimension;
+        }
+
+        @Override
+        public Rect get(View object) {
+            final Rect tempRect = mTempRect;
+            if (!object.getClipBounds(tempRect)) {
+                tempRect.setEmpty();
+            }
+            return tempRect;
+        }
+
+        @Override
+        public void set(View object, Rect value) {
+            final Rect tempRect = mTempRect;
+            if (object.getClipBounds(tempRect)) {
+                if (mTargetDimension == TARGET_X) {
+                    tempRect.left = value.left;
+                    tempRect.right = value.right;
+                } else {
+                    tempRect.top = value.top;
+                    tempRect.bottom = value.bottom;
+                }
+                object.setClipBounds(tempRect);
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java
new file mode 100644 (file)
index 0000000..eac3ff8
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * 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.internal.transition;
+
+import com.android.internal.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+
+/**
+ * EpicenterTranslate captures the {@link View#getTranslationX()} and
+ * {@link View#getTranslationY()} before and after the scene change and
+ * animates between those and the epicenter's center during a visibility
+ * transition.
+ */
+public class EpicenterTranslate extends Visibility {
+    private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
+    private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX";
+    private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY";
+    private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ";
+    private static final String PROPNAME_Z = "android:epicenterReveal:z";
+
+    private final TimeInterpolator mInterpolatorX;
+    private final TimeInterpolator mInterpolatorY;
+    private final TimeInterpolator mInterpolatorZ;
+
+    public EpicenterTranslate() {
+        mInterpolatorX = null;
+        mInterpolatorY = null;
+        mInterpolatorZ = null;
+    }
+
+    public EpicenterTranslate(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.EpicenterTranslate, 0, 0);
+
+        final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0);
+        if (interpolatorX != 0) {
+            mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX);
+        } else {
+            mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN;
+        }
+
+        final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0);
+        if (interpolatorY != 0) {
+            mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY);
+        } else {
+            mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN;
+        }
+
+        final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0);
+        if (interpolatorZ != 0) {
+            mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ);
+        } else {
+            mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN;
+        }
+
+        a.recycle();
+    }
+
+    @Override
+    public void captureStartValues(TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    private void captureValues(TransitionValues values) {
+        final View view = values.view;
+        if (view.getVisibility() == View.GONE) {
+            return;
+        }
+
+        final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+        values.values.put(PROPNAME_BOUNDS, bounds);
+        values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX());
+        values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY());
+        values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ());
+        values.values.put(PROPNAME_Z, view.getZ());
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+
+        final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        final Rect start = getEpicenterOrCenter(end);
+        final float startX = start.centerX() - end.centerX();
+        final float startY = start.centerY() - end.centerY();
+        final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z);
+
+        // Translate the view to be centered on the epicenter.
+        view.setTranslationX(startX);
+        view.setTranslationY(startY);
+        view.setTranslationZ(startZ);
+
+        final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
+        final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
+        final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
+        return createAnimator(view, startX, startY, startZ, endX, endY, endZ,
+                mInterpolatorX, mInterpolatorY, mInterpolatorZ);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null) {
+            return null;
+        }
+
+        final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        final Rect end = getEpicenterOrCenter(start);
+        final float endX = end.centerX() - start.centerX();
+        final float endY = end.centerY() - start.centerY();
+        final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z);
+
+        final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X);
+        final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y);
+        final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z);
+        return createAnimator(view, startX, startY, startZ, endX, endY, endZ,
+                mInterpolatorX, mInterpolatorY, mInterpolatorZ);
+    }
+
+    private Rect getEpicenterOrCenter(Rect bestRect) {
+        final Rect epicenter = getEpicenter();
+        if (epicenter != null) {
+            return epicenter;
+        }
+
+        final int centerX = bestRect.centerX();
+        final int centerY = bestRect.centerY();
+        return new Rect(centerX, centerY, centerX, centerY);
+    }
+
+    private static Animator createAnimator(final View view, float startX, float startY,
+            float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX,
+            TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) {
+        final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX);
+        if (interpolatorX != null) {
+            animX.setInterpolator(interpolatorX);
+        }
+
+        final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY);
+        if (interpolatorY != null) {
+            animY.setInterpolator(interpolatorY);
+        }
+
+        final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
+        if (interpolatorZ != null) {
+            animZ.setInterpolator(interpolatorZ);
+        }
+
+        final AnimatorSet animSet = new AnimatorSet();
+        animSet.playTogether(animX, animY, animZ);
+        return animSet;
+    }
+}
diff --git a/core/java/com/android/internal/transition/TransitionConstants.java b/core/java/com/android/internal/transition/TransitionConstants.java
new file mode 100644 (file)
index 0000000..e9015e6
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.transition;
+
+import android.animation.TimeInterpolator;
+import android.view.animation.PathInterpolator;
+
+class TransitionConstants {
+    static final TimeInterpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0, 0, 0.2f, 1);
+    static final TimeInterpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0, 0.2f, 1);
+}
index 92d4c1e..38c41f0 100644 (file)
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
                android:transitionOrdering="together">
-     <transition class="com.android.internal.transition.EpicenterClipReveal"
-         android:interpolator="@android:interpolator/accelerate_cubic"
-         android:startDelay="50"
-         android:duration="300" />
-     <fade
-         android:interpolator="@android:interpolator/linear"
-         android:duration="100" />
+    <!-- Start from location of epicenter, move to popup location. -->
+    <transition
+        class="com.android.internal.transition.EpicenterTranslate"
+        android:duration="300" />
+
+    <!-- Start from size of epicenter, expand to full width/height. -->
+    <transition
+        class="com.android.internal.transition.EpicenterClipReveal"
+        android:centerClipBounds="true"
+        android:duration="300" />
+
+    <!-- Quickly fade in. -->
+    <fade android:duration="100" />
 </transitionSet>
index 5cb9f0b..d54d79e 100644 (file)
@@ -13,5 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
+<!-- Fade out moderately fast. -->
 <fade xmlns:android="http://schemas.android.com/apk/res/android"
-      android:interpolator="@android:interpolator/linear" />
+      android:duration="300" />
index eb37619..8a92daf 100644 (file)
         <attr name="matchOrder" format="string" />
     </declare-styleable>
 
+    <!-- @hide For internal use only. Use only as directed. -->
+    <declare-styleable name="EpicenterClipReveal">
+        <attr name="centerClipBounds" format="boolean" />
+        <attr name="interpolatorX" format="reference" />
+        <attr name="interpolatorY" format="reference" />
+    </declare-styleable>
+
+    <!-- @hide For internal use only. Use only as directed. -->
+    <declare-styleable name="EpicenterTranslate">
+        <attr name="interpolatorX" />
+        <attr name="interpolatorY" />
+        <attr name="interpolatorZ" format="reference" />
+    </declare-styleable>
+
     <!-- Use <code>fade</code>as the root tag of the XML resource that
          describes a {@link android.transition.Fade Fade} transition.
          The attributes of the {@link android.R.styleable#Transition Transition}