diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 8dde4d318..26fcbabe7 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -1293,6 +1293,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/drawable-nodpi/ball.jpg b/samples/ApiDemos/res/drawable-nodpi/ball.jpg
new file mode 100644
index 000000000..2960b7309
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/ball.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/block.jpg b/samples/ApiDemos/res/drawable-nodpi/block.jpg
new file mode 100644
index 000000000..04c22a0cf
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/block.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/ducky.jpg b/samples/ApiDemos/res/drawable-nodpi/ducky.jpg
new file mode 100644
index 000000000..830bbe347
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/ducky.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/jellies.jpg b/samples/ApiDemos/res/drawable-nodpi/jellies.jpg
new file mode 100644
index 000000000..ee2b5c68c
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/jellies.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/mug.jpg b/samples/ApiDemos/res/drawable-nodpi/mug.jpg
new file mode 100644
index 000000000..e149e198a
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/mug.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/pencil.jpg b/samples/ApiDemos/res/drawable-nodpi/pencil.jpg
new file mode 100644
index 000000000..e348311bf
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/pencil.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/scissors.jpg b/samples/ApiDemos/res/drawable-nodpi/scissors.jpg
new file mode 100644
index 000000000..caf0ce8e2
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/scissors.jpg differ
diff --git a/samples/ApiDemos/res/drawable-nodpi/woot.jpg b/samples/ApiDemos/res/drawable-nodpi/woot.jpg
new file mode 100644
index 000000000..ccaef674b
Binary files /dev/null and b/samples/ApiDemos/res/drawable-nodpi/woot.jpg differ
diff --git a/samples/ApiDemos/res/layout/image_block.xml b/samples/ApiDemos/res/layout/image_block.xml
new file mode 100644
index 000000000..50d7d049e
--- /dev/null
+++ b/samples/ApiDemos/res/layout/image_block.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/image_details.xml b/samples/ApiDemos/res/layout/image_details.xml
new file mode 100644
index 000000000..a1a745921
--- /dev/null
+++ b/samples/ApiDemos/res/layout/image_details.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ApiDemos/res/layout/transition_scene1.xml b/samples/ApiDemos/res/layout/transition_scene1.xml
index a98da60b9..19fbe890a 100644
--- a/samples/ApiDemos/res/layout/transition_scene1.xml
+++ b/samples/ApiDemos/res/layout/transition_scene1.xml
@@ -18,36 +18,40 @@
android:layout_height="match_parent"
android:id="@+id/container">
-
-
-
-
diff --git a/samples/ApiDemos/res/layout/transition_scene2.xml b/samples/ApiDemos/res/layout/transition_scene2.xml
index fda227651..2d7e546f7 100644
--- a/samples/ApiDemos/res/layout/transition_scene2.xml
+++ b/samples/ApiDemos/res/layout/transition_scene2.xml
@@ -18,36 +18,40 @@
android:layout_height="match_parent"
android:id="@+id/container">
-
-
-
-
diff --git a/samples/ApiDemos/res/layout/transition_scene3.xml b/samples/ApiDemos/res/layout/transition_scene3.xml
index 39c4da770..43ffc386e 100644
--- a/samples/ApiDemos/res/layout/transition_scene3.xml
+++ b/samples/ApiDemos/res/layout/transition_scene3.xml
@@ -18,36 +18,40 @@
android:layout_height="match_parent"
android:id="@+id/container">
-
-
-
-
+
+
+
diff --git a/samples/ApiDemos/res/values/colors.xml b/samples/ApiDemos/res/values/colors.xml
index f2534d17e..fd68af39e 100644
--- a/samples/ApiDemos/res/values/colors.xml
+++ b/samples/ApiDemos/res/values/colors.xml
@@ -28,5 +28,6 @@
#0000ff
#f0f0
#ffffff00
+ #ff884488
diff --git a/samples/ApiDemos/res/values/styles.xml b/samples/ApiDemos/res/values/styles.xml
index 49a1c25a5..4d7894f1e 100644
--- a/samples/ApiDemos/res/values/styles.xml
+++ b/samples/ApiDemos/res/values/styles.xml
@@ -121,4 +121,13 @@
- wrap_content
- wrap_content
+
+
+
diff --git a/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransition.java b/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransition.java
new file mode 100644
index 000000000..c3171c43e
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransition.java
@@ -0,0 +1,198 @@
+/*
+ * 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.apis.animation;
+
+import com.example.android.apis.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.Window;
+import android.widget.ImageView;
+
+import java.util.Random;
+
+/**
+ *
+ */
+public class ActivityTransition extends Activity {
+ private static final String TAG = "ActivityTransition";
+
+ private static final String KEY_LEFT_ON_SCREEN = "ViewTransitionValues:left:";
+ private static final String KEY_TOP_ON_SCREEN = "ViewTransitionValues:top:";
+ private static final String KEY_WIDTH = "ViewTransitionValues:width:";
+ private static final String KEY_HEIGHT = "ViewTransitionValues:height:";
+ private static final String KEY_ID = "ViewTransitionValues:id";
+
+ private Random mRandom = new Random();
+ private boolean mComeBack;
+ private Fall mFall;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS);
+ setEarlyBackgroundTransition(false);
+ getWindow().setBackgroundDrawable(new ColorDrawable(randomColor()));
+ setContentView(R.layout.image_block);
+ View hero = getHero();
+ if (hero != null) {
+ hero.setSharedElementName("hero");
+ }
+ TransitionManager transitionManager = getContentTransitionManager();
+ TransitionSet transitions = new TransitionSet();
+ Fall fall = new Fall();
+ fall.setDuration(600);
+ fall.setStartDelay(600);
+ fall.setHero(hero);
+ transitions.addTransition(fall);
+ transitions.addTransition(new Up());
+ transitionManager.setTransition("null", getContentScene(), transitions);
+
+ transitions = new TransitionSet();
+ mFall = new Fall();
+ mFall.setDuration(600);
+ transitions.addTransition(mFall);
+ transitions.addTransition(new Up());
+ transitionManager.setTransition(getContentScene(), "null", transitions);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mComeBack) {
+ mComeBack = false;
+ setContentView(R.layout.image_block);
+ }
+ }
+
+ private View getHero() {
+ Bundle transitionArgs = getTransitionArgs();
+ if (transitionArgs == null) {
+ return null;
+ }
+ int id = transitionArgs.getInt(KEY_ID);
+ return findViewById(id);
+ }
+
+ private static Property DRAWABLE_BOUNDS
+ = new Property(Rect.class, "bounds") {
+ @Override
+ public Rect get(Drawable object) {
+ return null;
+ }
+
+ @Override
+ public void set(Drawable object, Rect value) {
+ object.setBounds(value);
+ object.invalidateSelf();
+ }
+ };
+
+ @Override
+ public void startSharedElementTransition(Bundle transitionArgs) {
+ final ImageView hero = (ImageView)getHero();
+ hero.setSharedElementName(null);
+ hero.setVisibility(View.INVISIBLE);
+
+ int[] loc = new int[2];
+ hero.getLocationOnScreen(loc);
+ int endScreenLeft = loc[0];
+ int endScreenTop = loc[1];
+ int originalWidth = hero.getWidth();
+ int originalHeight = hero.getHeight();
+
+ hero.setVisibility(View.INVISIBLE);
+ ViewGroup sceneRoot = getContentScene().getSceneRoot();
+ sceneRoot.getLocationOnScreen(loc);
+ int overlayLeft = loc[0];
+ int overlayTop = loc[1];
+ final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+
+ int endX = endScreenLeft - overlayLeft;
+ int endY = endScreenTop - overlayTop;
+
+ int startX = transitionArgs.getInt(KEY_LEFT_ON_SCREEN) - overlayLeft;
+ int startY = transitionArgs.getInt(KEY_TOP_ON_SCREEN) - overlayTop;
+ int startWidth = transitionArgs.getInt(KEY_WIDTH);
+ int startHeight = transitionArgs.getInt(KEY_HEIGHT);
+
+ int endHeight = originalWidth * startHeight / startWidth;
+ final Drawable image = hero.getDrawable();
+ Rect startBounds = new Rect(startX, startY, startX + startWidth, startY + startHeight);
+ endY += originalHeight - endHeight;
+ Rect endBounds = new Rect(endX, endY, endX + originalWidth, endY + endHeight);
+ ObjectAnimator boundsAnimator = ObjectAnimator.ofObject(image, DRAWABLE_BOUNDS,
+ new RectEvaluator(new Rect()), startBounds, endBounds);
+ hero.setImageDrawable(null);
+ image.setBounds(startBounds);
+ overlay.add(image);
+
+ boundsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ overlay.remove(image);
+ hero.setImageDrawable(image);
+ hero.setVisibility(View.VISIBLE);
+ }
+ });
+ boundsAnimator.start();
+ }
+
+ public void clicked(View v) {
+ v.setSharedElementName("hero");
+ mFall.setHero(v);
+ Intent intent = new Intent(this, ActivityTransitionDetails.class);
+ Bundle args = getHeroInfo(v);
+ ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(args);
+ startActivity(intent, options.toBundle());
+ //v.setTranslationZ(300);
+ mComeBack = true;
+ }
+
+ private int randomColor() {
+ int red = mRandom.nextInt(128);
+ int green = mRandom.nextInt(128);
+ int blue = mRandom.nextInt(128);
+ return 0xFF000000 | (red << 16) | (green << 8) | blue;
+ }
+
+ static Bundle getHeroInfo(View view) {
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_LEFT_ON_SCREEN, loc[0]);
+ bundle.putInt(KEY_TOP_ON_SCREEN, loc[1]);
+ bundle.putInt(KEY_WIDTH, view.getWidth());
+ bundle.putInt(KEY_HEIGHT, view.getHeight());
+ bundle.putInt(KEY_ID, view.getId());
+ return bundle;
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransitionDetails.java b/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransitionDetails.java
new file mode 100644
index 000000000..d0b2c4f5a
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/animation/ActivityTransitionDetails.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2014 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.apis.animation;
+
+import com.example.android.apis.R;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.ImageView;
+
+import java.util.Random;
+
+/**
+ *
+ */
+public class ActivityTransitionDetails extends Activity {
+ private static final String TAG = "ActivityTransitionDetails";
+
+ private static final String KEY_LEFT_ON_SCREEN = "ViewTransitionValues:left:";
+ private static final String KEY_TOP_ON_SCREEN = "ViewTransitionValues:top:";
+ private static final String KEY_WIDTH = "ViewTransitionValues:width:";
+ private static final String KEY_HEIGHT = "ViewTransitionValues:height:";
+ private static final String KEY_ID = "ViewTransitionValues:id";
+
+ private Random mRandom = new Random();
+ private boolean mComeBack;
+ private int mImageResourceId = R.drawable.ducky;
+ private int mId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS);
+ setEarlyBackgroundTransition(false);
+ getWindow().setBackgroundDrawable(new ColorDrawable(randomColor()));
+ setContentView(R.layout.image_details);
+ setImageMatrix();
+ ImageView hero = (ImageView)findViewById(R.id.titleImage);
+ hero.setImageDrawable(getHeroDrawable());
+ //hero.setTranslationZ(300);
+ TransitionManager transitionManager = getContentTransitionManager();
+ TransitionSet transitions = new TransitionSet();
+ Fall fall = new Fall();
+ fall.setDuration(600);
+ fall.setStartDelay(600);
+ transitions.addTransition(fall);
+ transitions.addTransition(new Up());
+ transitionManager.setTransition("null", getContentScene(), transitions);
+
+ transitions = new TransitionSet();
+ fall = new Fall();
+ fall.setDuration(600);
+ transitions.addTransition(fall);
+ transitions.addTransition(new Up());
+ transitionManager.setTransition(getContentScene(), "null", transitions);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (mComeBack) {
+ mComeBack = false;
+ setContentView(R.layout.image_details);
+ ImageView hero = (ImageView)findViewById(R.id.titleImage);
+ hero.setImageDrawable(getHeroDrawable());
+ setImageMatrix();
+ }
+ }
+
+ private Drawable getHeroDrawable() {
+ Bundle args = getTransitionArgs();
+ int id = args == null ? 0 : args.getInt(KEY_ID);
+ int resourceId;
+ switch (id) {
+ case R.id.ducky: resourceId = R.drawable.ducky; break;
+ case R.id.jellies: resourceId = R.drawable.jellies; break;
+ case R.id.mug: resourceId = R.drawable.mug; break;
+ case R.id.pencil: resourceId = R.drawable.pencil; break;
+ case R.id.scissors: resourceId = R.drawable.scissors; break;
+ case R.id.woot: resourceId = R.drawable.woot; break;
+ case R.id.ball: resourceId = R.drawable.ball; break;
+ case R.id.block: resourceId = R.drawable.block; break;
+ default:
+ resourceId = mImageResourceId;
+ break;
+ }
+ mImageResourceId = resourceId;
+ return getResources().getDrawable(resourceId);
+ }
+
+ private static Property DRAWABLE_BOUNDS
+ = new Property(Rect.class, "bounds") {
+ @Override
+ public Rect get(Drawable object) {
+ return null;
+ }
+
+ @Override
+ public void set(Drawable object, Rect value) {
+ object.setBounds(value);
+ object.invalidateSelf();
+ }
+ };
+
+ private void setImageMatrix() {
+ getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
+ // Use an image matrix such that it aligns with the bottom.
+ final ImageView hero = (ImageView)findViewById(R.id.titleImage);
+ hero.setScaleType(ImageView.ScaleType.MATRIX);
+ Matrix matrix = hero.getImageMatrix();
+ int height = hero.getHeight();
+ int width = hero.getWidth();
+ Drawable image = hero.getDrawable();
+ int intrinsicHeight = image.getIntrinsicHeight();
+ int intrinsicWidth = image.getIntrinsicWidth();
+ int scaledHeight = intrinsicHeight * width / intrinsicWidth;
+ matrix.postTranslate(0, (height - scaledHeight) / 2);
+ hero.setImageMatrix(matrix);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void startSharedElementTransition(Bundle transitionArgs) {
+ mId = transitionArgs.getInt(KEY_ID);
+ final ImageView hero = (ImageView)findViewById(R.id.titleImage);
+ int[] loc = new int[2];
+ hero.getLocationOnScreen(loc);
+ int endScreenLeft = loc[0];
+ int endScreenTop = loc[1];
+ int originalWidth = hero.getWidth();
+ int originalHeight = hero.getHeight();
+
+ hero.setVisibility(View.INVISIBLE);
+ ViewGroup sceneRoot = getContentScene().getSceneRoot();
+ sceneRoot.getLocationOnScreen(loc);
+ int overlayLeft = loc[0];
+ int overlayTop = loc[1];
+ final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+
+ int endX = endScreenLeft - overlayLeft;
+ int endY = endScreenTop - overlayTop;
+
+ int startX = transitionArgs.getInt(KEY_LEFT_ON_SCREEN) - overlayLeft;
+ int startY = transitionArgs.getInt(KEY_TOP_ON_SCREEN) - overlayTop;
+ int startWidth = transitionArgs.getInt(KEY_WIDTH);
+ int startHeight = transitionArgs.getInt(KEY_HEIGHT);
+
+ int endHeight = originalWidth * startHeight / startWidth;
+ final Drawable image = hero.getDrawable();
+ Rect startBounds = new Rect(startX, startY, startX + startWidth, startY + startHeight);
+ endY += originalHeight - endHeight;
+ Rect endBounds = new Rect(endX, endY, endX + originalWidth, endY + endHeight);
+ ObjectAnimator boundsAnimator = ObjectAnimator.ofObject(image, DRAWABLE_BOUNDS,
+ new RectEvaluator(new Rect()), startBounds, endBounds);
+ hero.setImageDrawable(null);
+ image.setBounds(startBounds);
+ overlay.add(image);
+
+ boundsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ overlay.remove(image);
+ hero.setImageDrawable(image);
+ hero.setVisibility(View.VISIBLE);
+ }
+ });
+ boundsAnimator.start();
+ }
+
+ public void clicked(View v) {
+ Intent intent = new Intent(this, ActivityTransition.class);
+ Bundle args = getHeroInfo((ImageView)v);
+ ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(args);
+ startActivity(intent, options.toBundle());
+ mComeBack = true;
+ }
+
+ private int randomColor() {
+ int red = mRandom.nextInt(128);
+ int green = mRandom.nextInt(128);
+ int blue = mRandom.nextInt(128);
+ return 0xFF000000 | (red << 16) | (green << 8) | blue;
+ }
+
+ private Bundle getHeroInfo(ImageView view) {
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ Bundle bundle = new Bundle();
+ Drawable image = view.getDrawable();
+ int intrinsicWidth = image.getIntrinsicWidth();
+ int intrinsicHeight = image.getIntrinsicHeight();
+ int width = view.getWidth();
+ int height = intrinsicHeight * width / intrinsicWidth;
+ int top = loc[1] + view.getHeight() - height;
+ bundle.putInt(KEY_LEFT_ON_SCREEN, loc[0]);
+ bundle.putInt(KEY_TOP_ON_SCREEN, top);
+ bundle.putInt(KEY_WIDTH, width);
+ bundle.putInt(KEY_HEIGHT, height);
+ bundle.putInt(KEY_ID, mId);
+ return bundle;
+ }
+
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/animation/Fall.java b/samples/ApiDemos/src/com/example/android/apis/animation/Fall.java
new file mode 100644
index 000000000..e7281b383
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/animation/Fall.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2014 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.apis.animation;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ *
+ */
+public class Fall extends Visibility {
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+ private static final String TAG = "Fall";
+ private static final String PROPNAME_SCREEN_LOCATION = "android:fade:screen_location";
+
+ private View mHero;
+
+ public void setHero(View hero) {
+ mHero = hero;
+ }
+
+ private Animator createAnimation(final View view, long startDelay, final float startY,
+ float endY, AnimatorListenerAdapter listener, TimeInterpolator interpolator) {
+ if (startY == endY) {
+ // run listener if we're noop'ing the animation, to get the end-state results now
+ if (listener != null) {
+ listener.onAnimationEnd(null);
+ }
+ return null;
+ }
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY);
+
+ if (listener != null) {
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
+ }
+ anim.setInterpolator(interpolator);
+ anim.setStartDelay(startDelay);
+ AnimatorSet wrapper = new AnimatorSet();
+ wrapper.play(anim);
+ wrapper.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ view.setTranslationY(startY);
+ }
+ });
+ return wrapper;
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ int[] loc = new int[2];
+ transitionValues.view.getLocationOnScreen(loc);
+ transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot,
+ TransitionValues startValues, int startVisibility,
+ TransitionValues endValues, int endVisibility) {
+ if (endValues == null) {
+ return null;
+ }
+ final View endView = endValues.view;
+ Log.v(TAG, "onAppear: " + endView.getId());
+ final float endY = endView.getTranslationY();
+ final float startY = endY + sceneRoot.getHeight();
+
+ TransitionListener transitionListener = new TransitionListener() {
+ boolean mCanceled = false;
+ float mPausedY;
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ endView.setTranslationY(endY);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ if (!mCanceled) {
+ endView.setTranslationY(endY);
+ }
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ mPausedY = endView.getTranslationY();
+ endView.setTranslationY(endY);
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ endView.setTranslationY(mPausedY);
+ }
+ };
+ addListener(transitionListener);
+ int[] loc = (int[]) endValues.values.get(PROPNAME_SCREEN_LOCATION);
+ long startDelay = calculateRiseStartDelay(sceneRoot, endView, loc);
+ return createAnimation(endView, startDelay, startY, endY, null, sDecelerate);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot,
+ TransitionValues startValues, int startVisibility,
+ TransitionValues endValues, int endVisibility) {
+ View view = null;
+ View startView = (startValues != null) ? startValues.view : null;
+ View endView = (endValues != null) ? endValues.view : null;
+ View overlayView = null;
+ View viewToKeep = null;
+ if (endView == null || endView.getParent() == null) {
+ if (endView != null) {
+ // endView was removed from its parent - add it to the overlay
+ view = overlayView = endView;
+ } else if (startView != null) {
+ // endView does not exist. Use startView only under certain
+ // conditions, because placing a view in an overlay necessitates
+ // it being removed from its current parent
+ if (startView.getParent() == null) {
+ // no parent - safe to use
+ view = overlayView = startView;
+ } else if (startView.getParent() instanceof View &&
+ startView.getParent().getParent() == null) {
+ View startParent = (View) startView.getParent();
+ int id = startParent.getId();
+ if (id != View.NO_ID && sceneRoot.findViewById(id) != null && canRemoveViews()) {
+ // no parent, but its parent is unparented but the parent
+ // hierarchy has been replaced by a new hierarchy with the same id
+ // and it is safe to un-parent startView
+ view = overlayView = startView;
+ }
+ }
+ }
+ } else {
+ // visibility change
+ if (endVisibility == View.INVISIBLE) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ // Becoming GONE
+ if (startView == endView) {
+ view = endView;
+ viewToKeep = view;
+ } else {
+ view = startView;
+ overlayView = view;
+ }
+ }
+ }
+ final int finalVisibility = endVisibility;
+
+ int[] loc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
+ // TODO: add automatic facility to Visibility superclass for keeping views around
+ if (overlayView != null) {
+ // TODO: Need to do this for general case of adding to overlay
+ long startDelay = calculateFallStartDelay(sceneRoot, overlayView, loc);
+ int screenX = loc[0];
+ int screenY = loc[1];
+ loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+ overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
+ sceneRoot.getOverlay().add(overlayView);
+ // TODO: add automatic facility to Visibility superclass for keeping views around
+ final float startY = overlayView.getTranslationY();
+ float endY = startY + sceneRoot.getHeight();
+ final View finalView = view;
+ final View finalOverlayView = overlayView;
+ final View finalViewToKeep = viewToKeep;
+ final ViewGroup finalSceneRoot = sceneRoot;
+ final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finalView.setTranslationY(startY);
+ // TODO: restore view offset from overlay repositioning
+ if (finalViewToKeep != null) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ if (finalOverlayView != null) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ if (finalOverlayView != null) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ if (finalOverlayView != null) {
+ finalSceneRoot.getOverlay().add(finalOverlayView);
+ }
+ }
+ };
+ return createAnimation(view, startDelay, startY, endY, endListener, sAccelerate);
+ }
+ if (viewToKeep != null) {
+ long startDelay = calculateFallStartDelay(sceneRoot, viewToKeep, loc);
+ // TODO: find a different way to do this, like just changing the view to be
+ // VISIBLE for the duration of the transition
+ viewToKeep.setVisibility((View.VISIBLE));
+ // TODO: add automatic facility to Visibility superclass for keeping views around
+ final float startY = viewToKeep.getTranslationY();
+ float endY = startY + sceneRoot.getHeight();
+ final View finalView = view;
+ final View finalOverlayView = overlayView;
+ final View finalViewToKeep = viewToKeep;
+ final ViewGroup finalSceneRoot = sceneRoot;
+ final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+ boolean mCanceled = false;
+ float mPausedY = -1;
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ if (finalViewToKeep != null && !mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ mPausedY = finalView.getTranslationY();
+ finalView.setTranslationY(startY);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ if (finalViewToKeep != null && !mCanceled) {
+ finalViewToKeep.setVisibility(View.VISIBLE);
+ }
+ finalView.setTranslationY(mPausedY);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ if (mPausedY >= 0) {
+ finalView.setTranslationY(mPausedY);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCanceled) {
+ finalView.setTranslationY(startY);
+ }
+ // TODO: restore view offset from overlay repositioning
+ if (finalViewToKeep != null && !mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ if (finalOverlayView != null) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+ }
+ };
+ return createAnimation(view, startDelay, startY, endY, endListener, sAccelerate);
+ }
+ return null;
+ }
+
+ private long calculateFallStartDelay(View sceneRoot, View view, int[] viewLoc) {
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ int bottom = loc[1] + sceneRoot.getHeight();
+ float distance = bottom - viewLoc[1] + view.getTranslationY();
+ if (mHero != null) {
+ mHero.getLocationOnScreen(loc);
+ float heroX = loc[0] + mHero.getTranslationX() + (mHero.getWidth() / 2.0f);
+ float viewX = viewLoc[0] + view.getTranslationX() + (view.getWidth() / 2.0f);
+ float distanceX = Math.abs(heroX - viewX);
+ float distanceXRatio = distanceX / sceneRoot.getWidth();
+ distance += (1 - distanceXRatio) * mHero.getHeight();
+ }
+ float distanceRatio = distance/sceneRoot.getHeight() / 3;
+ return Math.max(0, Math.round(distanceRatio * getDuration()));
+ }
+
+ private long calculateRiseStartDelay(View sceneRoot, View view, int[] viewLoc) {
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ int top = loc[1];
+ float distance = viewLoc[1] + view.getTranslationY() - top;
+ if (mHero != null) {
+ mHero.getLocationOnScreen(loc);
+ float heroX = loc[0] + mHero.getTranslationX() + (mHero.getWidth() / 2.0f);
+ float viewX = viewLoc[0] + view.getTranslationX() + (view.getWidth() / 2.0f);
+ float distanceX = Math.abs(heroX - viewX);
+ float distanceXRatio = distanceX / sceneRoot.getWidth();
+ distance += distanceXRatio * mHero.getHeight();
+ }
+ float distanceRatio = distance/sceneRoot.getHeight() / 3;
+ return Math.max(0, Math.round(distanceRatio * getDuration()));
+ }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/animation/Up.java b/samples/ApiDemos/src/com/example/android/apis/animation/Up.java
new file mode 100644
index 000000000..4d3bba5b8
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/animation/Up.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 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.apis.animation;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ *
+ */
+public class Up extends Transition {
+ private static final String PROPNAME_Z = "android:z:height";
+ private static final String[] sTransitionProperties = {
+ PROPNAME_Z,
+ };
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ transitionValues.values.put(PROPNAME_Z, view.getTranslationZ());
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ final float startZ = (Float)startValues.values.get(PROPNAME_Z);
+ final float endZ = (Float)endValues.values.get(PROPNAME_Z);
+ if (startZ == endZ) {
+ return null;
+ }
+ final View view = endValues.view;
+
+ TransitionListener transitionListener = new TransitionListener() {
+ boolean mCanceled = false;
+ float mPausedZ;
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ view.setTranslationZ(endZ);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ if (!mCanceled) {
+ view.setTranslationZ(endZ);
+ }
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ mPausedZ = view.getTranslationZ();
+ view.setTranslationZ(endZ);
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ view.setTranslationZ(mPausedZ);
+ }
+ };
+ addListener(transitionListener);
+
+ return ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ);
+ }
+}