From 608b87d9e57b71a86374a439bf5c3febd1e142f2 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 15 Apr 2014 09:01:32 -0700 Subject: [PATCH] Add Transform and ClipBounds Transitions. Made MoveImage use an overlay view. Drawables cover all Views in the overlay and there may be a desire to order the overlays. Change-Id: Ic7b81f0d26d8cce3f475c2eebbce01538bc55d46 --- api/current.txt | 12 ++ core/java/android/transition/ChangeClipBounds.java | 96 ++++++++++++ core/java/android/transition/ChangeTransform.java | 163 +++++++++++++++++++++ core/java/android/transition/MoveImage.java | 22 ++- core/java/android/transition/Transition.java | 11 +- .../android/transition/TransitionInflater.java | 6 + 6 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 core/java/android/transition/ChangeClipBounds.java create mode 100644 core/java/android/transition/ChangeTransform.java diff --git a/api/current.txt b/api/current.txt index b81c83347b7e..e65b048e0fc5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27952,6 +27952,18 @@ package android.transition { method public void setResizeClip(boolean); } + public class ChangeClipBounds extends android.transition.Transition { + ctor public ChangeClipBounds(); + method public void captureEndValues(android.transition.TransitionValues); + method public void captureStartValues(android.transition.TransitionValues); + } + + public class ChangeTransform extends android.transition.Transition { + ctor public ChangeTransform(); + method public void captureEndValues(android.transition.TransitionValues); + method public void captureStartValues(android.transition.TransitionValues); + } + public class CircularPropagation extends android.transition.VisibilityPropagation { ctor public CircularPropagation(); method public long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues); diff --git a/core/java/android/transition/ChangeClipBounds.java b/core/java/android/transition/ChangeClipBounds.java new file mode 100644 index 000000000000..a61b29dbe6ba --- /dev/null +++ b/core/java/android/transition/ChangeClipBounds.java @@ -0,0 +1,96 @@ +/* + * 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 android.transition; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +/** + * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the + * scene change and animates those changes during the transition. + */ +public class ChangeClipBounds extends Transition { + + private static final String TAG = "ChangeTransform"; + + private static final String PROPNAME_CLIP = "android:clipBounds:clip"; + private static final String PROPNAME_BOUNDS = "android:clipBounds:bounds"; + + private static final String[] sTransitionProperties = { + PROPNAME_CLIP, + }; + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + private void captureValues(TransitionValues values) { + View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + if (clip == null) { + Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null + || !startValues.values.containsKey(PROPNAME_CLIP) + || !endValues.values.containsKey(PROPNAME_CLIP)) { + return null; + } + Rect start = (Rect) startValues.values.get(PROPNAME_CLIP); + Rect end = (Rect) endValues.values.get(PROPNAME_CLIP); + if (start == null && end == null) { + return null; // No animation required since there is no clip. + } + + if (start == null) { + start = (Rect) startValues.values.get(PROPNAME_BOUNDS); + } else if (end == null) { + end = (Rect) endValues.values.get(PROPNAME_BOUNDS); + } + if (start.equals(end)) { + return null; + } + + endValues.view.setClipBounds(start); + RectEvaluator evaluator = new RectEvaluator(new Rect()); + return ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end); + } +} diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java new file mode 100644 index 000000000000..85cb2c7862ea --- /dev/null +++ b/core/java/android/transition/ChangeTransform.java @@ -0,0 +1,163 @@ +/* + * 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 android.transition; + +import android.animation.Animator; +import android.animation.FloatArrayEvaluator; +import android.animation.ObjectAnimator; +import android.util.FloatProperty; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; + +/** + * This Transition captures scale and rotation for Views before and after the + * scene change and animates those changes during the transition. + * + *

ChangeTransform does not work when the pivot changes between scenes, so either the + * pivot must be set to prevent automatic pivot adjustment or the View's size must be unchanged.

+ */ +public class ChangeTransform extends Transition { + + private static final String TAG = "ChangeTransform"; + + private static final String PROPNAME_SCALE_X = "android:changeTransform:scaleX"; + private static final String PROPNAME_SCALE_Y = "android:changeTransform:scaleY"; + private static final String PROPNAME_ROTATION_X = "android:changeTransform:rotationX"; + private static final String PROPNAME_ROTATION_Y = "android:changeTransform:rotationY"; + private static final String PROPNAME_ROTATION_Z = "android:changeTransform:rotationZ"; + private static final String PROPNAME_PIVOT_X = "android:changeTransform:pivotX"; + private static final String PROPNAME_PIVOT_Y = "android:changeTransform:pivotY"; + + private static final String[] sTransitionProperties = { + PROPNAME_SCALE_X, + PROPNAME_SCALE_Y, + PROPNAME_ROTATION_X, + PROPNAME_ROTATION_Y, + PROPNAME_ROTATION_Z, + }; + + private static final FloatProperty[] sChangedProperties = new FloatProperty[] { + (FloatProperty) View.SCALE_X, + (FloatProperty) View.SCALE_Y, + (FloatProperty) View.ROTATION_X, + (FloatProperty) View.ROTATION_Y, + (FloatProperty) View.ROTATION, + }; + + private static Property TRANSFORMS = new Property(float[].class, + "transforms") { + @Override + public float[] get(View object) { + return null; + } + + @Override + public void set(View view, float[] values) { + for (int i = 0; i < values.length; i++) { + float value = values[i]; + if (value != Float.NaN) { + sChangedProperties[i].setValue(view, value); + } + } + } + }; + + @Override + public String[] getTransitionProperties() { + return sTransitionProperties; + } + + private void captureValues(TransitionValues values) { + View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + values.values.put(PROPNAME_SCALE_X, view.getScaleX()); + values.values.put(PROPNAME_SCALE_Y, view.getScaleY()); + values.values.put(PROPNAME_PIVOT_X, view.getPivotX()); + values.values.put(PROPNAME_PIVOT_Y, view.getPivotY()); + values.values.put(PROPNAME_ROTATION_X, view.getRotationX()); + values.values.put(PROPNAME_ROTATION_Y, view.getRotationY()); + values.values.put(PROPNAME_ROTATION_Z, view.getRotation()); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (startValues == null || endValues == null + || !startValues.values.containsKey(PROPNAME_SCALE_X) + || !endValues.values.containsKey(PROPNAME_SCALE_X) + || !isPivotSame(startValues, endValues) + || !isChanged(startValues, endValues)) { + return null; + } + + float[] start = createValues(startValues); + float[] end = createValues(endValues); + for (int i = 0; i < start.length; i++) { + if (start[i] == end[i]) { + start[i] = Float.NaN; + end[i] = Float.NaN; + } else { + sChangedProperties[i].setValue(endValues.view, start[i]); + } + } + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[start.length]); + return ObjectAnimator.ofObject(endValues.view, TRANSFORMS, evaluator, start, end); + } + + private static float[] createValues(TransitionValues transitionValues) { + float[] values = new float[sChangedProperties.length]; + for (int i = 0; i < values.length; i++) { + values[i] = (Float) transitionValues.values.get(sTransitionProperties[i]); + } + return values; + } + + private static boolean isPivotSame(TransitionValues startValues, TransitionValues endValues) { + float startPivotX = (Float) startValues.values.get(PROPNAME_PIVOT_X); + float startPivotY = (Float) startValues.values.get(PROPNAME_PIVOT_Y); + float endPivotX = (Float) endValues.values.get(PROPNAME_PIVOT_X); + float endPivotY = (Float) endValues.values.get(PROPNAME_PIVOT_Y); + + // We don't support pivot changes, because they could be automatically set + // and we can't end the state in an automatic state. + return startPivotX == endPivotX && startPivotY == endPivotY; + } + + private static boolean isChanged(TransitionValues startValues, TransitionValues endValues) { + for (int i = 0; i < sChangedProperties.length; i++) { + Object start = startValues.values.get(sTransitionProperties[i]); + Object end = endValues.values.get(sTransitionProperties[i]); + if (!start.equals(end)) { + return true; + } + } + return false; + } +} diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java index d68e97190a36..e4c393995618 100644 --- a/core/java/android/transition/MoveImage.java +++ b/core/java/android/transition/MoveImage.java @@ -170,13 +170,20 @@ public class MoveImage extends Transition { drawable = drawable.getConstantState().newDrawable(); final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable); + final ImageView overlayImage = new ImageView(imageView.getContext()); + final ViewGroupOverlay overlay = sceneRoot.getOverlay(); + overlay.add(overlayImage); + overlayImage.setLeft(0); + overlayImage.setTop(0); + overlayImage.setRight(sceneRoot.getWidth()); + overlayImage.setBottom(sceneRoot.getBottom()); + overlayImage.setScaleType(ImageView.ScaleType.MATRIX); + overlayImage.setImageDrawable(matrixClippedDrawable); matrixClippedDrawable.setMatrix(startMatrix); matrixClippedDrawable.setBounds(startBounds); matrixClippedDrawable.setClipRect(startClip); imageView.setVisibility(View.INVISIBLE); - final ViewGroupOverlay overlay = sceneRoot.getOverlay(); - overlay.add(matrixClippedDrawable); ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable, changes.toArray(new PropertyValuesHolder[changes.size()])); @@ -184,19 +191,24 @@ public class MoveImage extends Transition { @Override public void onAnimationEnd(Animator animation) { imageView.setVisibility(View.VISIBLE); - overlay.remove(matrixClippedDrawable); + overlay.remove(overlayImage); } @Override public void onAnimationPause(Animator animation) { imageView.setVisibility(View.VISIBLE); - overlay.remove(matrixClippedDrawable); + overlayImage.setVisibility(View.INVISIBLE); } @Override public void onAnimationResume(Animator animation) { imageView.setVisibility(View.INVISIBLE); - overlay.add(matrixClippedDrawable); + overlayImage.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + onAnimationEnd(animation); } }; diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index d8392f5812c0..b93312bf2b54 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -66,13 +66,12 @@ import java.util.List; * * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds} * - *

{@link android.transition.Explode} transition:

+ *

This TransitionSet contains {@link android.transition.Explode} for visibility, + * {@link android.transition.ChangeBounds}, {@link android.transition.ChangeTransform}, + * and {@link android.transition.ChangeClipBounds} for non-ImageViews and + * {@link android.transition.MoveImage} for ImageViews:

* - * {@sample development/samples/ApiDemos/res/transition/explode.xml Explode} - * - *

{@link android.transition.MoveImage} transition:

- * - * {@sample development/samples/ApiDemos/res/transition/move_image.xml MoveImage} + * {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform} * *

Note that attributes for the transition are not required, just as they are * optional when declared in code; Transitions created from XML resources will use diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 1aa412a3c4d8..2bdba81f5bf7 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -153,6 +153,12 @@ public class TransitionInflater { } else if ("moveImage".equals(name)) { transition = new MoveImage(); newTransition = true; + } else if ("changeTransform".equals(name)) { + transition = new ChangeTransform(); + newTransition = true; + } else if ("changeClipBounds".equals(name)) { + transition = new ChangeClipBounds(); + newTransition = true; } else if ("autoTransition".equals(name)) { transition = new AutoTransition(); newTransition = true; -- 2.11.0