summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/transition/ArcMotion.java272
-rw-r--r--core/java/android/transition/AutoTransition.java12
-rw-r--r--core/java/android/transition/ChangeBounds.java112
-rw-r--r--core/java/android/transition/ChangeClipBounds.java8
-rw-r--r--core/java/android/transition/ChangeImageTransform.java14
-rw-r--r--core/java/android/transition/ChangeTransform.java8
-rw-r--r--core/java/android/transition/Explode.java6
-rw-r--r--core/java/android/transition/Fade.java12
-rw-r--r--core/java/android/transition/MoveImage.java12
-rw-r--r--core/java/android/transition/PathMotion.java59
-rw-r--r--core/java/android/transition/PatternMotion.java149
-rw-r--r--core/java/android/transition/Recolor.java8
-rw-r--r--core/java/android/transition/Slide.java27
-rw-r--r--core/java/android/transition/Transition.java120
-rw-r--r--core/java/android/transition/TransitionInflater.java262
-rw-r--r--core/java/android/transition/TransitionSet.java23
-rw-r--r--core/java/android/transition/Visibility.java30
17 files changed, 915 insertions, 219 deletions
diff --git a/core/java/android/transition/ArcMotion.java b/core/java/android/transition/ArcMotion.java
new file mode 100644
index 0000000..a27063d
--- /dev/null
+++ b/core/java/android/transition/ArcMotion.java
@@ -0,0 +1,272 @@
+/*
+ * 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 com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+
+/**
+ * A PathMotion that generates a curved path along an arc on an imaginary circle containing
+ * the two points. If the horizontal distance between the points is less than the vertical
+ * distance, then the circle's center point will be horizontally aligned with the end point. If the
+ * vertical distance is less than the horizontal distance then the circle's center point
+ * will be vertically aligned with the end point.
+ * <p>
+ * When the two points are near horizontal or vertical, the curve of the motion will be
+ * small as the center of the circle will be far from both points. To force curvature of
+ * the path, {@link #setMinimumHorizontalAngle(float)} and
+ * {@link #setMinimumVerticalAngle(float)} may be used to set the minimum angle of the
+ * arc between two points.
+ * </p>
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>
+ * {@code
+ * &lt;changeBounds>
+ * &lt;arcMotion android:minimumHorizontalAngle="15"
+ * android:minimumVerticalAngle="0" android:maximumAngle="90"/>
+ * &lt;/changeBounds>
+ * }
+ * </pre>
+ */
+public class ArcMotion extends PathMotion {
+
+ private static final float DEFAULT_MIN_ANGLE_DEGREES = 0;
+ private static final float DEFAULT_MAX_ANGLE_DEGREES = 70;
+ private static final float DEFAULT_MAX_TANGENT = (float)
+ Math.tan(Math.toRadians(DEFAULT_MAX_ANGLE_DEGREES/2));
+
+ private float mMinimumHorizontalAngle = 0;
+ private float mMinimumVerticalAngle = 0;
+ private float mMaximumAngle = DEFAULT_MAX_ANGLE_DEGREES;
+ private float mMinimumHorizontalTangent = 0;
+ private float mMinimumVerticalTangent = 0;
+ private float mMaximumTangent = DEFAULT_MAX_TANGENT;
+
+ public ArcMotion() {}
+
+ public ArcMotion(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ArcMotion);
+ float minimumVerticalAngle = a.getFloat(R.styleable.ArcMotion_minimumVerticalAngle,
+ DEFAULT_MIN_ANGLE_DEGREES);
+ setMinimumVerticalAngle(minimumVerticalAngle);
+ float minimumHorizontalAngle = a.getFloat(R.styleable.ArcMotion_minimumHorizontalAngle,
+ DEFAULT_MIN_ANGLE_DEGREES);
+ setMinimumHorizontalAngle(minimumHorizontalAngle);
+ float maximumAngle = a.getFloat(R.styleable.ArcMotion_maximumAngle,
+ DEFAULT_MAX_ANGLE_DEGREES);
+ setMaximumAngle(maximumAngle);
+ a.recycle();
+ }
+
+ /**
+ * Sets the minimum arc along the circle between two points aligned near horizontally.
+ * When start and end points are close to horizontal, the calculated center point of the
+ * circle will be far from both points, giving a near straight path between the points.
+ * By setting a minimum angle, this forces the center point to be closer and give an
+ * exaggerated curve to the path.
+ * <p>The default value is 0.</p>
+ *
+ * @param angleInDegrees The minimum angle of the arc on a circle describing the Path
+ * between two nearly horizontally-separated points.
+ * @attr ref android.R.styleable#ArcMotion_minimumHorizontalAngle
+ */
+ public void setMinimumHorizontalAngle(float angleInDegrees) {
+ mMinimumHorizontalAngle = angleInDegrees;
+ mMinimumHorizontalTangent = toTangent(angleInDegrees);
+ }
+
+ /**
+ * Returns the minimum arc along the circle between two points aligned near horizontally.
+ * When start and end points are close to horizontal, the calculated center point of the
+ * circle will be far from both points, giving a near straight path between the points.
+ * By setting a minimum angle, this forces the center point to be closer and give an
+ * exaggerated curve to the path.
+ * <p>The default value is 0.</p>
+ *
+ * @return The minimum arc along the circle between two points aligned near horizontally.
+ * @attr ref android.R.styleable#ArcMotion_minimumHorizontalAngle
+ */
+ public float getMinimumHorizontalAngle() {
+ return mMinimumHorizontalAngle;
+ }
+
+ /**
+ * Sets the minimum arc along the circle between two points aligned near vertically.
+ * When start and end points are close to vertical, the calculated center point of the
+ * circle will be far from both points, giving a near straight path between the points.
+ * By setting a minimum angle, this forces the center point to be closer and give an
+ * exaggerated curve to the path.
+ * <p>The default value is 0.</p>
+ *
+ * @param angleInDegrees The minimum angle of the arc on a circle describing the Path
+ * between two nearly vertically-separated points.
+ * @attr ref android.R.styleable#ArcMotion_minimumVerticalAngle
+ */
+ public void setMinimumVerticalAngle(float angleInDegrees) {
+ mMinimumVerticalAngle = angleInDegrees;
+ mMinimumVerticalTangent = toTangent(angleInDegrees);
+ }
+
+ /**
+ * Returns the minimum arc along the circle between two points aligned near vertically.
+ * When start and end points are close to vertical, the calculated center point of the
+ * circle will be far from both points, giving a near straight path between the points.
+ * By setting a minimum angle, this forces the center point to be closer and give an
+ * exaggerated curve to the path.
+ * <p>The default value is 0.</p>
+ *
+ * @return The minimum angle of the arc on a circle describing the Path
+ * between two nearly vertically-separated points.
+ * @attr ref android.R.styleable#ArcMotion_minimumVerticalAngle
+ */
+ public float getMinimumVerticalAngle() {
+ return mMinimumVerticalAngle;
+ }
+
+ /**
+ * Sets the maximum arc along the circle between two points. When start and end points
+ * have close to equal x and y differences, the curve between them is large. This forces
+ * the curved path to have an arc of at most the given angle.
+ * <p>The default value is 70 degrees.</p>
+ *
+ * @param angleInDegrees The maximum angle of the arc on a circle describing the Path
+ * between the start and end points.
+ * @attr ref android.R.styleable#ArcMotion_maximumAngle
+ */
+ public void setMaximumAngle(float angleInDegrees) {
+ mMaximumAngle = angleInDegrees;
+ mMaximumTangent = toTangent(angleInDegrees);
+ }
+
+ /**
+ * Returns the maximum arc along the circle between two points. When start and end points
+ * have close to equal x and y differences, the curve between them is large. This forces
+ * the curved path to have an arc of at most the given angle.
+ * <p>The default value is 70 degrees.</p>
+ *
+ * @return The maximum angle of the arc on a circle describing the Path
+ * between the start and end points.
+ * @attr ref android.R.styleable#ArcMotion_maximumAngle
+ */
+ public float getMaximumAngle() {
+ return mMaximumAngle;
+ }
+
+ private static float toTangent(float arcInDegrees) {
+ if (arcInDegrees < 0 || arcInDegrees > 90) {
+ throw new IllegalArgumentException("Arc must be between 0 and 90 degrees");
+ }
+ return (float) Math.tan(Math.toRadians(arcInDegrees / 2));
+ }
+
+ @Override
+ public Path getPath(float startX, float startY, float endX, float endY) {
+ // Here's a little ascii art to show how this is calculated:
+ // c---------- b
+ // \ / |
+ // \ d |
+ // \ / e
+ // a----f
+ // This diagram assumes that the horizontal distance is less than the vertical
+ // distance between The start point (a) and end point (b).
+ // d is the midpoint between a and b. c is the center point of the circle with
+ // This path is formed by assuming that start and end points are in
+ // an arc on a circle. The end point is centered in the circle vertically
+ // and start is a point on the circle.
+
+ // Triangles bfa and bde form similar right triangles. The control points
+ // for the cubic Bezier arc path are the midpoints between a and e and e and b.
+
+ Path path = new Path();
+ path.moveTo(startX, startY);
+
+ float ex;
+ float ey;
+ if (startY == endY) {
+ ex = (startX + endX) / 2;
+ ey = startY + mMinimumHorizontalTangent * Math.abs(endX - startX) / 2;
+ } else if (startX == endX) {
+ ex = startX + mMinimumVerticalTangent * Math.abs(endY - startY) / 2;
+ ey = (startY + endY) / 2;
+ } else {
+ float deltaX = endX - startX;
+ float deltaY = startY - endY; // Y is inverted compared to diagram above.
+ // hypotenuse squared.
+ float h2 = deltaX * deltaX + deltaY * deltaY;
+
+ // Midpoint between start and end
+ float dx = (startX + endX) / 2;
+ float dy = (startY + endY) / 2;
+
+ // Distance squared between end point and mid point is (1/2 hypotenuse)^2
+ float midDist2 = h2 * 0.25f;
+
+ float minimumArcDist2 = 0;
+
+ if (Math.abs(deltaX) < Math.abs(deltaY)) {
+ // Similar triangles bfa and bde mean that (ab/fb = eb/bd)
+ // Therefore, eb = ab * bd / fb
+ // ab = hypotenuse
+ // bd = hypotenuse/2
+ // fb = deltaY
+ float eDistY = h2 / (2 * deltaY);
+ ey = endY + eDistY;
+ ex = endX;
+
+ minimumArcDist2 = midDist2 * mMinimumVerticalTangent
+ * mMinimumVerticalTangent;
+ } else {
+ // Same as above, but flip X & Y
+ float eDistX = h2 / (2 * deltaX);
+ ex = endX + eDistX;
+ ey = endY;
+
+ minimumArcDist2 = midDist2 * mMinimumHorizontalTangent
+ * mMinimumHorizontalTangent;
+ }
+ float arcDistX = dx - ex;
+ float arcDistY = dy - ey;
+ float arcDist2 = arcDistX * arcDistX + arcDistY * arcDistY;
+
+ float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
+
+ float newArcDistance2 = 0;
+ if (arcDist2 < minimumArcDist2) {
+ newArcDistance2 = minimumArcDist2;
+ } else if (arcDist2 > maximumArcDist2) {
+ newArcDistance2 = maximumArcDist2;
+ }
+ if (newArcDistance2 != 0) {
+ float ratio2 = newArcDistance2 / arcDist2;
+ float ratio = FloatMath.sqrt(ratio2);
+ ex = dx + (ratio * (ex - dx));
+ ey = dy + (ratio * (ey - dy));
+ }
+ }
+ float controlX1 = (startX + ex) / 2;
+ float controlY1 = (startY + ey) / 2;
+ float controlX2 = (ex + endX) / 2;
+ float controlY2 = (ey + endY) / 2;
+ path.cubicTo(controlX1, controlY1, controlX2, controlY2, endX, endY);
+ return path;
+ }
+}
diff --git a/core/java/android/transition/AutoTransition.java b/core/java/android/transition/AutoTransition.java
index 6e46021..2104737 100644
--- a/core/java/android/transition/AutoTransition.java
+++ b/core/java/android/transition/AutoTransition.java
@@ -16,6 +16,9 @@
package android.transition;
+import android.content.Context;
+import android.util.AttributeSet;
+
/**
* Utility class for creating a default transition that automatically fades,
* moves, and resizes views during a scene change.
@@ -33,6 +36,15 @@ public class AutoTransition extends TransitionSet {
*
*/
public AutoTransition() {
+ init();
+ }
+
+ public AutoTransition(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index 8053bff..2591e24 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -16,15 +16,24 @@
package android.transition;
+import android.animation.TypeConverter;
+import android.content.Context;
+import android.graphics.PointF;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.RectEvaluator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
@@ -51,6 +60,24 @@ public class ChangeBounds extends Transition {
PROPNAME_WINDOW_Y
};
+ private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
+ new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
+ private Rect mBounds = new Rect();
+
+ @Override
+ public void set(Drawable object, PointF value) {
+ object.copyBounds(mBounds);
+ mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
+ object.setBounds(mBounds);
+ }
+
+ @Override
+ public PointF get(Drawable object) {
+ object.copyBounds(mBounds);
+ return new PointF(mBounds.left, mBounds.top);
+ }
+ };
+
int[] tempLocation = new int[2];
boolean mResizeClip = false;
boolean mReparent = false;
@@ -58,6 +85,12 @@ public class ChangeBounds extends Transition {
private static RectEvaluator sRectEvaluator = new RectEvaluator();
+ public ChangeBounds() {}
+
+ public ChangeBounds(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
@@ -138,34 +171,29 @@ public class ChangeBounds extends Transition {
int endHeight = endBottom - endTop;
int numChanges = 0;
if (startWidth != 0 && startHeight != 0 && endWidth != 0 && endHeight != 0) {
- if (startLeft != endLeft) ++numChanges;
- if (startTop != endTop) ++numChanges;
- if (startRight != endRight) ++numChanges;
- if (startBottom != endBottom) ++numChanges;
+ if (startLeft != endLeft || startTop != endTop) ++numChanges;
+ if (startRight != endRight || startBottom != endBottom) ++numChanges;
}
if (numChanges > 0) {
if (!mResizeClip) {
- PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
- int pvhIndex = 0;
if (startLeft != endLeft) view.setLeft(startLeft);
if (startTop != endTop) view.setTop(startTop);
if (startRight != endRight) view.setRight(startRight);
if (startBottom != endBottom) view.setBottom(startBottom);
- if (startLeft != endLeft) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofInt("left", startLeft, endLeft);
- }
- if (startTop != endTop) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop);
+ ObjectAnimator topLeftAnimator = null;
+ if (startLeft != endLeft || startTop != endTop) {
+ Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+ endLeft, endTop);
+ topLeftAnimator = ObjectAnimator.ofInt(view, "left", "top", topLeftPath);
}
- if (startRight != endRight) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right",
- startRight, endRight);
+ ObjectAnimator bottomRightAnimator = null;
+ if (startRight != endRight || startBottom != endBottom) {
+ Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
+ endRight, endBottom);
+ bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
+ bottomRightPath);
}
- if (startBottom != endBottom) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofInt("bottom",
- startBottom, endBottom);
- }
- ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
+ Animator anim = mergeAnimators(topLeftAnimator, bottomRightAnimator);
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
@@ -215,23 +243,20 @@ public class ChangeBounds extends Transition {
if (transXDelta != 0) numChanges++;
if (transYDelta != 0) numChanges++;
if (widthDelta != 0 || heightDelta != 0) numChanges++;
- PropertyValuesHolder pvh[] = new PropertyValuesHolder[numChanges];
- int pvhIndex = 0;
- if (transXDelta != 0) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationX",
- view.getTranslationX(), 0);
- }
- if (transYDelta != 0) {
- pvh[pvhIndex++] = PropertyValuesHolder.ofFloat("translationY",
- view.getTranslationY(), 0);
+ ObjectAnimator translationAnimator = null;
+ if (transXDelta != 0 || transYDelta != 0) {
+ Path topLeftPath = getPathMotion().getPath(0, 0, transXDelta, transYDelta);
+ translationAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ View.TRANSLATION_Y, topLeftPath);
}
+ ObjectAnimator clipAnimator = null;
if (widthDelta != 0 || heightDelta != 0) {
Rect tempStartBounds = new Rect(0, 0, startWidth, startHeight);
Rect tempEndBounds = new Rect(0, 0, endWidth, endHeight);
- pvh[pvhIndex++] = PropertyValuesHolder.ofObject("clipBounds",
- sRectEvaluator, tempStartBounds, tempEndBounds);
+ clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
+ tempStartBounds, tempEndBounds);
}
- ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
+ Animator anim = mergeAnimators(translationAnimator, clipAnimator);
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
@@ -287,14 +312,11 @@ public class ChangeBounds extends Transition {
final BitmapDrawable drawable = new BitmapDrawable(bitmap);
view.setVisibility(View.INVISIBLE);
sceneRoot.getOverlay().add(drawable);
- Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1],
- startX - tempLocation[0] + view.getWidth(),
- startY - tempLocation[1] + view.getHeight());
- Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1],
- endX - tempLocation[0] + view.getWidth(),
- endY - tempLocation[1] + view.getHeight());
- ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
- sRectEvaluator, startBounds1, endBounds1);
+ Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
+ startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
+ PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
+ DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -307,4 +329,16 @@ public class ChangeBounds extends Transition {
}
return null;
}
+
+ private static Animator mergeAnimators(Animator animator1, Animator animator2) {
+ if (animator1 == null) {
+ return animator2;
+ } else if (animator2 == null) {
+ return animator1;
+ } else {
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(animator1, animator2);
+ return animatorSet;
+ }
+ }
}
diff --git a/core/java/android/transition/ChangeClipBounds.java b/core/java/android/transition/ChangeClipBounds.java
index a61b29d..8d0943c 100644
--- a/core/java/android/transition/ChangeClipBounds.java
+++ b/core/java/android/transition/ChangeClipBounds.java
@@ -18,7 +18,9 @@ package android.transition;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.RectEvaluator;
+import android.content.Context;
import android.graphics.Rect;
+import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -37,6 +39,12 @@ public class ChangeClipBounds extends Transition {
PROPNAME_CLIP,
};
+ public ChangeClipBounds() {}
+
+ public ChangeClipBounds(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
diff --git a/core/java/android/transition/ChangeImageTransform.java b/core/java/android/transition/ChangeImageTransform.java
index b003690..12437d7 100644
--- a/core/java/android/transition/ChangeImageTransform.java
+++ b/core/java/android/transition/ChangeImageTransform.java
@@ -19,9 +19,11 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
+import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
@@ -37,10 +39,10 @@ import java.util.Map;
*/
public class ChangeImageTransform extends Transition {
- private static final String TAG = "ChangeScaleType";
+ private static final String TAG = "ChangeImageTransform";
- private static final String PROPNAME_MATRIX = "android:changeScaleType:matrix";
- private static final String PROPNAME_BOUNDS = "android:changeScaleType:bounds";
+ private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
+ private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds";
private static final String[] sTransitionProperties = {
PROPNAME_MATRIX,
@@ -67,6 +69,12 @@ public class ChangeImageTransform extends Transition {
}
};
+ public ChangeImageTransform() {}
+
+ public ChangeImageTransform(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
diff --git a/core/java/android/transition/ChangeTransform.java b/core/java/android/transition/ChangeTransform.java
index 8c03e82..e9be3b9 100644
--- a/core/java/android/transition/ChangeTransform.java
+++ b/core/java/android/transition/ChangeTransform.java
@@ -18,6 +18,8 @@ package android.transition;
import android.animation.Animator;
import android.animation.FloatArrayEvaluator;
import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Property;
import android.view.View;
@@ -76,6 +78,12 @@ public class ChangeTransform extends Transition {
}
};
+ public ChangeTransform() {}
+
+ public ChangeTransform(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
index feb8efd..995702e 100644
--- a/core/java/android/transition/Explode.java
+++ b/core/java/android/transition/Explode.java
@@ -19,7 +19,9 @@ import com.android.internal.R;
import android.animation.Animator;
import android.animation.TimeInterpolator;
+import android.content.Context;
import android.graphics.Rect;
+import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.View;
import android.view.ViewGroup;
@@ -48,6 +50,10 @@ public class Explode extends Visibility {
setPropagation(new CircularPropagation());
}
+ public Explode(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
view.getLocationOnScreen(mTempLoc);
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 71559da..420c248 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -16,9 +16,14 @@
package android.transition;
+import com.android.internal.R;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -93,6 +98,13 @@ public class Fade extends Visibility {
setMode(fadingMode);
}
+ public Fade(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Fade);
+ int fadingMode = a.getInt(R.styleable.Fade_fadingMode, getMode());
+ setMode(fadingMode);
+ }
+
/**
* Utility method to handle creating and running the Animator.
*/
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
index 48b96ec..2f7945d 100644
--- a/core/java/android/transition/MoveImage.java
+++ b/core/java/android/transition/MoveImage.java
@@ -15,6 +15,9 @@
*/
package android.transition;
+import android.content.Context;
+import android.util.AttributeSet;
+
/**
* TO BE REMOVED.
* Use ChangeImageTransform + ChangeBounds instead.
@@ -23,6 +26,15 @@ package android.transition;
public class MoveImage extends TransitionSet {
public MoveImage() {
+ init();
+ }
+
+ public MoveImage(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
addTransition(new ChangeBounds());
addTransition(new ChangeClipBounds());
addTransition(new ChangeTransform());
diff --git a/core/java/android/transition/PathMotion.java b/core/java/android/transition/PathMotion.java
new file mode 100644
index 0000000..651223d
--- /dev/null
+++ b/core/java/android/transition/PathMotion.java
@@ -0,0 +1,59 @@
+/*
+ * 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.content.Context;
+import android.graphics.Path;
+import android.util.AttributeSet;
+
+/**
+ * This base class can be extended to provide motion along a Path to Transitions.
+ *
+ * <p>
+ * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
+ * in a straight path between the start and end positions. Applications that desire to
+ * have these motions move in a curve can change how Views interpolate in two dimensions
+ * by extending PathMotion and implementing {@link #getPath(float, float, float, float)}.
+ * </p>
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>
+ * {@code
+ * &lt;changeBounds>
+ * &lt;pathMotion class="my.app.transition.MyPathMotion"/>
+ * &lt;/changeBounds>
+ * }
+ * </pre>
+ */
+public abstract class PathMotion {
+
+ public PathMotion() {}
+
+ public PathMotion(Context context, AttributeSet attrs) {}
+
+ /**
+ * Provide a Path to interpolate between two points <code>(startX, startY)</code> and
+ * <code>(endX, endY)</code>. This allows controlled curved motion along two dimensions.
+ *
+ * @param startX The x coordinate of the starting point.
+ * @param startY The y coordinate of the starting point.
+ * @param endX The x coordinate of the ending point.
+ * @param endY The y coordinate of the ending point.
+ * @return A Path along which the points should be interpolated. The returned Path
+ * must start at point <code>(startX, startY)</code>, typically using
+ * {@link android.graphics.Path#moveTo(float, float)} and end at <code>(endX, endY)</code>.
+ */
+ public abstract Path getPath(float startX, float startY, float endX, float endY);
+}
diff --git a/core/java/android/transition/PatternMotion.java b/core/java/android/transition/PatternMotion.java
new file mode 100644
index 0000000..e4045b4
--- /dev/null
+++ b/core/java/android/transition/PatternMotion.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.internal.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+import android.util.PathParser;
+
+/**
+ * A PathMotion that takes a Path pattern and applies it to the separation between two points.
+ * The starting point of the Path will be moved to the origin and the end point will be scaled
+ * and rotated so that it matches with the target end point.
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>
+ * {@code
+ * &lt;changeBounds>
+ * &lt;patternMotion android:pathData="M0 0 L0 100 L100 100"/>
+ * &lt;/changeBounds>
+ * }
+ * </pre>
+ */
+public class PatternMotion extends PathMotion {
+
+ private Path mOriginalPattern;
+
+ private final Path mPattern = new Path();
+
+ private final Matrix mTempMatrix = new Matrix();
+
+ /**
+ * Constructs a PatternMotion with a straight-line pattern.
+ */
+ public PatternMotion() {
+ mPattern.lineTo(1, 0);
+ mOriginalPattern = mPattern;
+ }
+
+ public PatternMotion(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PatternMotion);
+ try {
+ String pathData = a.getString(R.styleable.PatternMotion_pathData);
+ if (pathData == null) {
+ throw new RuntimeException("pathData must be supplied for patternMotion");
+ }
+ Path pattern = PathParser.createPathFromPathData(pathData);
+ setPattern(pattern);
+ } finally {
+ a.recycle();
+ }
+
+ }
+
+ /**
+ * Creates a PatternMotion with the Path defining a pattern of motion between two coordinates.
+ * The pattern will be translated, rotated, and scaled to fit between the start and end points.
+ * The pattern must not be empty and must have the end point differ from the start point.
+ *
+ * @param pattern A Path to be used as a pattern for two-dimensional motion.
+ */
+ public PatternMotion(Path pattern) {
+ setPattern(pattern);
+ }
+
+ /**
+ * Returns the Path defining a pattern of motion between two coordinates.
+ * The pattern will be translated, rotated, and scaled to fit between the start and end points.
+ * The pattern must not be empty and must have the end point differ from the start point.
+ *
+ * @return the Path defining a pattern of motion between two coordinates.
+ * @attr ref android.R.styleable#PatternMotion_pathData
+ */
+ public Path getPattern() {
+ return mOriginalPattern;
+ }
+
+ /**
+ * Sets the Path defining a pattern of motion between two coordinates.
+ * The pattern will be translated, rotated, and scaled to fit between the start and end points.
+ * The pattern must not be empty and must have the end point differ from the start point.
+ *
+ * @param pattern A Path to be used as a pattern for two-dimensional motion.
+ * @attr ref android.R.styleable#PatternMotion_pathData
+ */
+ public void setPattern(Path pattern) {
+ PathMeasure pathMeasure = new PathMeasure(pattern, false);
+ float length = pathMeasure.getLength();
+ float[] pos = new float[2];
+ pathMeasure.getPosTan(length, pos, null);
+ float endX = pos[0];
+ float endY = pos[1];
+ pathMeasure.getPosTan(0, pos, null);
+ float startX = pos[0];
+ float startY = pos[1];
+
+ if (startX == endX && startY == endY) {
+ throw new IllegalArgumentException("pattern must not end at the starting point");
+ }
+
+ mTempMatrix.setTranslate(-startX, -startY);
+ float dx = endX - startX;
+ float dy = endY - startY;
+ float distance = distance(dx, dy);
+ float scale = 1 / distance;
+ mTempMatrix.postScale(scale, scale);
+ double angle = Math.atan2(dy, dx);
+ mTempMatrix.postRotate((float) Math.toDegrees(-angle));
+ pattern.transform(mTempMatrix, mPattern);
+ mOriginalPattern = pattern;
+ }
+
+ @Override
+ public Path getPath(float startX, float startY, float endX, float endY) {
+ float dx = endX - startX;
+ float dy = endY - startY;
+ float length = distance(dx, dy);
+ double angle = Math.atan2(dy, dx);
+
+ mTempMatrix.setScale(length, length);
+ mTempMatrix.postRotate((float) Math.toDegrees(angle));
+ mTempMatrix.postTranslate(startX, startY);
+ Path path = new Path();
+ mPattern.transform(mTempMatrix, path);
+ return path;
+ }
+
+ private static float distance(float x, float y) {
+ return FloatMath.sqrt((x * x) + (y * y));
+ }
+}
diff --git a/core/java/android/transition/Recolor.java b/core/java/android/transition/Recolor.java
index 1638f67..1a6864a 100644
--- a/core/java/android/transition/Recolor.java
+++ b/core/java/android/transition/Recolor.java
@@ -18,8 +18,10 @@ package android.transition;
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -40,6 +42,12 @@ public class Recolor extends Transition {
private static final String PROPNAME_BACKGROUND = "android:recolor:background";
private static final String PROPNAME_TEXT_COLOR = "android:recolor:textColor";
+ public Recolor() {}
+
+ public Recolor(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
private void captureValues(TransitionValues transitionValues) {
transitionValues.values.put(PROPNAME_BACKGROUND, transitionValues.view.getBackground());
if (transitionValues.view instanceof TextView) {
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index 0d2e487..ae2e4aa 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -17,11 +17,15 @@ package android.transition;
import android.animation.Animator;
import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
+import com.android.internal.R;
/**
* This transition tracks changes to the visibility of target views in the
@@ -38,6 +42,7 @@ public class Slide extends Visibility {
private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
private CalculateSlide mSlideCalculator = sCalculateBottom;
+ private int mSlideEdge = Gravity.BOTTOM;
private interface CalculateSlide {
@@ -107,6 +112,14 @@ public class Slide extends Visibility {
setSlideEdge(slideEdge);
}
+ public Slide(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slide);
+ int edge = a.getInt(R.styleable.Slide_slideEdge, Gravity.BOTTOM);
+ a.recycle();
+ setSlideEdge(edge);
+ }
+
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
int[] position = new int[2];
@@ -132,6 +145,7 @@ public class Slide extends Visibility {
* @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
* {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
* {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}.
+ * @attr ref android.R.styleable#Slide_slideEdge
*/
public void setSlideEdge(int slideEdge) {
switch (slideEdge) {
@@ -150,11 +164,24 @@ public class Slide extends Visibility {
default:
throw new IllegalArgumentException("Invalid slide direction");
}
+ mSlideEdge = slideEdge;
SidePropagation propagation = new SidePropagation();
propagation.setSide(slideEdge);
setPropagation(propagation);
}
+ /**
+ * Returns the edge that Views appear and disappear from.
+ *
+ * @return the edge of the scene to use for Views appearing and disappearing. One of
+ * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+ * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM}.
+ * @attr ref android.R.styleable#Slide_slideEdge
+ */
+ public int getSlideEdge() {
+ return mSlideEdge;
+ }
+
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 52e6691..880f559 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -19,23 +19,32 @@ package android.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Path;
import android.graphics.Rect;
import android.util.ArrayMap;
+import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseLongArray;
+import android.view.InflateException;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOverlay;
import android.view.WindowId;
+import android.view.animation.AnimationUtils;
import android.widget.ListView;
import android.widget.Spinner;
import java.util.ArrayList;
import java.util.List;
+import java.util.StringTokenizer;
+
+import com.android.internal.R;
/**
* A Transition holds information about animations that will be run on its
@@ -139,6 +148,13 @@ public abstract class Transition implements Cloneable {
private static final int MATCH_LAST = MATCH_ITEM_ID;
+ private static final String MATCH_INSTANCE_STR = "instance";
+ private static final String MATCH_NAME_STR = "name";
+ /** To be removed before L release */
+ private static final String MATCH_VIEW_NAME_STR = "viewName";
+ private static final String MATCH_ID_STR = "id";
+ private static final String MATCH_ITEM_ID_STR = "itemId";
+
private static final int[] DEFAULT_MATCH_ORDER = {
MATCH_NAME,
MATCH_INSTANCE,
@@ -146,6 +162,16 @@ public abstract class Transition implements Cloneable {
MATCH_ITEM_ID,
};
+ private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
+ @Override
+ public Path getPath(float startX, float startY, float endX, float endY) {
+ Path path = new Path();
+ path.moveTo(startX, startY);
+ path.lineTo(endX, endY);
+ return path;
+ }
+ };
+
private String mName = getClass().getName();
long mStartDelay = -1;
@@ -215,6 +241,10 @@ public abstract class Transition implements Cloneable {
// transitionNames.
ArrayMap<String, String> mNameOverrides;
+ // The function used to interpolate along two-dimensional points. Typically used
+ // for adding curves to x/y View motion.
+ private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
+
/**
* Constructs a Transition object with no target objects. A transition with
* no targets defaults to running on all target objects in the scene hierarchy
@@ -224,6 +254,66 @@ public abstract class Transition implements Cloneable {
public Transition() {}
/**
+ * Perform inflation from XML and apply a class-specific base style from a
+ * theme attribute or style resource. This constructor of Transition allows
+ * subclasses to use their own base style when they are inflating.
+ *
+ * @param context The Context the transition is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the transition.
+ */
+ public Transition(Context context, AttributeSet attrs) {
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Transition);
+ long duration = a.getInt(R.styleable.Transition_duration, -1);
+ if (duration >= 0) {
+ setDuration(duration);
+ }
+ long startDelay = a.getInt(R.styleable.Transition_startDelay, -1);
+ if (startDelay > 0) {
+ setStartDelay(startDelay);
+ }
+ final int resID = a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
+ if (resID > 0) {
+ setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+ }
+ String matchOrder = a.getString(R.styleable.Transition_matchOrder);
+ if (matchOrder != null) {
+ setMatchOrder(parseMatchOrder(matchOrder));
+ }
+ a.recycle();
+ }
+
+ private static int[] parseMatchOrder(String matchOrderString) {
+ StringTokenizer st = new StringTokenizer(matchOrderString, ",");
+ int matches[] = new int[st.countTokens()];
+ int index = 0;
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken().trim();
+ if (MATCH_ID_STR.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_ID;
+ } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_INSTANCE;
+ } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_NAME;
+ } else if (MATCH_VIEW_NAME_STR.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_NAME;
+ } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
+ matches[index] = Transition.MATCH_ITEM_ID;
+ } else if (token.isEmpty()) {
+ int[] smallerMatches = new int[matches.length - 1];
+ System.arraycopy(matches, 0, smallerMatches, 0, index);
+ matches = smallerMatches;
+ index--;
+ } else {
+ throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
+ }
+ index++;
+ }
+ return matches;
+ }
+
+ /**
* Sets the duration of this transition. By default, there is no duration
* (indicated by a negative number), which means that the Animator created by
* the transition will have its own specified duration. If the duration of a
@@ -1840,6 +1930,36 @@ public abstract class Transition implements Cloneable {
}
/**
+ * Sets the algorithm used to calculate two-dimensional interpolation.
+ * <p>
+ * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
+ * in a straight path between the start and end positions. Applications that desire to
+ * have these motions move in a curve can change how Views interpolate in two dimensions
+ * by extending PathMotion and implementing
+ * {@link android.transition.PathMotion#getPath(float, float, float, float)}.
+ * </p>
+ * @param pathMotion Algorithm object to use for determining how to interpolate in two
+ * dimensions. If null, a straight-path algorithm will be used.
+ */
+ public void setPathMotion(PathMotion pathMotion) {
+ if (pathMotion == null) {
+ mPathMotion = STRAIGHT_PATH_MOTION;
+ } else {
+ mPathMotion = pathMotion;
+ }
+ }
+
+ /**
+ * Returns the algorithm object used to interpolate along two dimensions. This is typically
+ * used to determine the View motion between two points.
+ *
+ * @return The algorithm object used to interpolate along two dimensions.
+ */
+ public PathMotion getPathMotion() {
+ return mPathMotion;
+ }
+
+ /**
* Sets the method for determining Animator start delays.
* When a Transition affects several Views like {@link android.transition.Explode} or
* {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index e0c3cae..fd3e450 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -16,21 +16,24 @@
package android.transition;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Xml;
-import android.view.Gravity;
import android.view.InflateException;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.StringTokenizer;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
/**
* This class inflates scenes and transitions from resource files.
@@ -41,12 +44,11 @@ import java.util.StringTokenizer;
* and {@link android.R.styleable#TransitionManager}.
*/
public class TransitionInflater {
- private static final String MATCH_INSTANCE = "instance";
- private static final String MATCH_NAME = "name";
- /** To be removed before L release */
- private static final String MATCH_VIEW_NAME = "viewName";
- private static final String MATCH_ID = "id";
- private static final String MATCH_ITEM_ID = "itemId";
+
+ private static final Class<?>[] sConstructorSignature = new Class[] {
+ Context.class, AttributeSet.class};
+ private final static ArrayMap<String, Constructor> sConstructors =
+ new ArrayMap<String, Constructor>();
private Context mContext;
@@ -118,9 +120,8 @@ public class TransitionInflater {
//
// Transition loading
//
-
private Transition createTransitionFromXml(XmlPullParser parser,
- AttributeSet attrs, TransitionSet transitionSet)
+ AttributeSet attrs, Transition parent)
throws XmlPullParserException, IOException {
Transition transition = null;
@@ -129,119 +130,107 @@ public class TransitionInflater {
int type;
int depth = parser.getDepth();
+ TransitionSet transitionSet = (parent instanceof TransitionSet)
+ ? (TransitionSet) parent : null;
+
while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
- boolean newTransition = false;
-
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if ("fade".equals(name)) {
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Fade);
- int fadingMode = a.getInt(com.android.internal.R.styleable.Fade_fadingMode,
- Fade.IN | Fade.OUT);
- transition = new Fade(fadingMode);
- newTransition = true;
+ transition = new Fade(mContext, attrs);
} else if ("changeBounds".equals(name)) {
- transition = new ChangeBounds();
- newTransition = true;
+ transition = new ChangeBounds(mContext, attrs);
} else if ("slide".equals(name)) {
- transition = createSlideTransition(attrs);
- newTransition = true;
+ transition = new Slide(mContext, attrs);
} else if ("explode".equals(name)) {
- transition = new Explode();
- newTransition = true;
+ transition = new Explode(mContext, attrs);
} else if ("moveImage".equals(name)) {
- transition = new MoveImage();
- newTransition = true;
+ transition = new MoveImage(mContext, attrs);
} else if ("changeImageTransform".equals(name)) {
- transition = new ChangeImageTransform();
- newTransition = true;
+ transition = new ChangeImageTransform(mContext, attrs);
} else if ("changeTransform".equals(name)) {
- transition = new ChangeTransform();
- newTransition = true;
+ transition = new ChangeTransform(mContext, attrs);
} else if ("changeClipBounds".equals(name)) {
- transition = new ChangeClipBounds();
- newTransition = true;
+ transition = new ChangeClipBounds(mContext, attrs);
} else if ("autoTransition".equals(name)) {
- transition = new AutoTransition();
- newTransition = true;
+ transition = new AutoTransition(mContext, attrs);
} else if ("recolor".equals(name)) {
- transition = new Recolor();
- newTransition = true;
+ transition = new Recolor(mContext, attrs);
} else if ("transitionSet".equals(name)) {
- transition = new TransitionSet();
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.TransitionSet);
- int ordering = a.getInt(
- com.android.internal.R.styleable.TransitionSet_transitionOrdering,
- TransitionSet.ORDERING_TOGETHER);
- ((TransitionSet) transition).setOrdering(ordering);
- createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
- a.recycle();
- newTransition = true;
+ transition = new TransitionSet(mContext, attrs);
} else if ("transition".equals(name)) {
- transition = createCustomTransition(attrs);
+ transition = (Transition) createCustom(attrs, Transition.class, "transition");
} else if ("targets".equals(name)) {
- if (parser.getDepth() - 1 > depth && transition != null) {
- // We're inside the child tag - add targets to the child
- getTargetIds(parser, attrs, transition);
- } else if (parser.getDepth() - 1 == depth && transitionSet != null) {
- // add targets to the set
- getTargetIds(parser, attrs, transitionSet);
- }
- }
- if (transition != null || "targets".equals(name)) {
- if (newTransition) {
- loadTransition(transition, attrs);
- if (transitionSet != null) {
- transitionSet.addTransition(transition);
- }
- }
+ getTargetIds(parser, attrs, parent);
+ } else if ("arcMotion".equals(name)) {
+ parent.setPathMotion(new ArcMotion(mContext, attrs));
+ } else if ("pathMotion".equals(name)) {
+ parent.setPathMotion((PathMotion)createCustom(attrs, PathMotion.class, "pathMotion"));
+ } else if ("patternMotion".equals(name)) {
+ parent.setPathMotion(new PatternMotion(mContext, attrs));
} else {
throw new RuntimeException("Unknown scene name: " + parser.getName());
}
+ if (transition != null) {
+ if (!parser.isEmptyElementTag()) {
+ createTransitionFromXml(parser, attrs, transition);
+ }
+ if (transitionSet != null) {
+ transitionSet.addTransition(transition);
+ transition = null;
+ } else if (parent != null) {
+ throw new InflateException("Could not add transition to another transition.");
+ }
+ }
}
return transition;
}
- private Transition createCustomTransition(AttributeSet attrs) {
+ private Object createCustom(AttributeSet attrs, Class expectedType, String tag) {
String className = attrs.getAttributeValue(null, "class");
if (className == null) {
- throw new RuntimeException("transition tag must have a 'class' attribute");
+ throw new InflateException(tag + " tag must have a 'class' attribute");
}
try {
- Class c = Class.forName(className);
- if (!Transition.class.isAssignableFrom(c)) {
- throw new RuntimeException("transition class must be a Transition: " + className);
+ synchronized (sConstructors) {
+ Constructor constructor = sConstructors.get(className);
+ if (constructor == null) {
+ Class c = mContext.getClassLoader().loadClass(className)
+ .asSubclass(expectedType);
+ if (c != null) {
+ constructor = c.getConstructor(sConstructorSignature);
+ sConstructors.put(className, constructor);
+ }
+ }
+
+ return constructor.newInstance(mContext, attrs);
}
- return (Transition) c.newInstance();
} catch (InstantiationException e) {
- throw new RuntimeException("Could not instantiate transition class", e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Could not access default constructor for transition class "
- + className, e);
+ throw new InflateException("Could not instantiate " + expectedType + " class " +
+ className, e);
} catch (ClassNotFoundException e) {
- throw new RuntimeException("Could not find transition class " + className, e);
+ throw new InflateException("Could not instantiate " + expectedType + " class " +
+ className, e);
+ } catch (InvocationTargetException e) {
+ throw new InflateException("Could not instantiate " + expectedType + " class " +
+ className, e);
+ } catch (NoSuchMethodException e) {
+ throw new InflateException("Could not instantiate " + expectedType + " class " +
+ className, e);
+ } catch (IllegalAccessException e) {
+ throw new InflateException("Could not instantiate " + expectedType + " class " +
+ className, e);
}
}
- private Slide createSlideTransition(AttributeSet attrs) {
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Slide);
- int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Gravity.BOTTOM);
- Slide slide = new Slide(edge);
- a.recycle();
- return slide;
- }
-
private void getTargetIds(XmlPullParser parser,
AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
@@ -258,34 +247,27 @@ public class TransitionInflater {
String name = parser.getName();
if (name.equals("target")) {
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.TransitionTarget);
- int id = a.getResourceId(
- com.android.internal.R.styleable.TransitionTarget_targetId, -1);
+ TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionTarget);
+ int id = a.getResourceId(R.styleable.TransitionTarget_targetId, 0);
String transitionName;
- if (id >= 0) {
+ if (id != 0) {
transition.addTarget(id);
- } else if ((id = a.getResourceId(
- com.android.internal.R.styleable.TransitionTarget_excludeId, -1)) >= 0) {
+ } else if ((id = a.getResourceId(R.styleable.TransitionTarget_excludeId, 0)) != 0) {
transition.excludeTarget(id, true);
- } else if ((transitionName = a.getString(
- com.android.internal.R.styleable.TransitionTarget_targetName))
+ } else if ((transitionName = a.getString(R.styleable.TransitionTarget_targetName))
!= null) {
transition.addTarget(transitionName);
- } else if ((transitionName = a.getString(
- com.android.internal.R.styleable.TransitionTarget_excludeName))
+ } else if ((transitionName = a.getString(R.styleable.TransitionTarget_excludeName))
!= null) {
transition.excludeTarget(transitionName, true);
} else {
- String className = a.getString(
- com.android.internal.R.styleable.TransitionTarget_excludeClass);
+ String className = a.getString(R.styleable.TransitionTarget_excludeClass);
try {
if (className != null) {
Class clazz = Class.forName(className);
transition.excludeTarget(clazz, true);
- } else if ((className = a.getString(
- com.android.internal.R.styleable.TransitionTarget_targetClass))
- != null) {
+ } else if ((className =
+ a.getString(R.styleable.TransitionTarget_targetClass)) != null) {
Class clazz = Class.forName(className);
transition.addTarget(clazz);
}
@@ -299,72 +281,6 @@ public class TransitionInflater {
}
}
- private int[] parseMatchOrder(String matchOrderString) {
- StringTokenizer st = new StringTokenizer(matchOrderString, ",");
- int matches[] = new int[st.countTokens()];
- int index = 0;
- while (st.hasMoreTokens()) {
- String token = st.nextToken().trim();
- if (MATCH_ID.equalsIgnoreCase(token)) {
- matches[index] = Transition.MATCH_ID;
- } else if (MATCH_INSTANCE.equalsIgnoreCase(token)) {
- matches[index] = Transition.MATCH_INSTANCE;
- } else if (MATCH_NAME.equalsIgnoreCase(token)) {
- matches[index] = Transition.MATCH_NAME;
- } else if (MATCH_VIEW_NAME.equalsIgnoreCase(token)) {
- matches[index] = Transition.MATCH_NAME;
- } else if (MATCH_ITEM_ID.equalsIgnoreCase(token)) {
- matches[index] = Transition.MATCH_ITEM_ID;
- } else if (token.isEmpty()) {
- int[] smallerMatches = new int[matches.length - 1];
- System.arraycopy(matches, 0, smallerMatches, 0, index);
- matches = smallerMatches;
- index--;
- } else {
- throw new RuntimeException("Unknown match type in matchOrder: '" + token + "'");
- }
- index++;
- }
- return matches;
- }
-
- private Transition loadTransition(Transition transition, AttributeSet attrs)
- throws Resources.NotFoundException {
-
- TypedArray a =
- mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Transition);
- long duration = a.getInt(com.android.internal.R.styleable.Transition_duration, -1);
- if (duration >= 0) {
- transition.setDuration(duration);
- }
- long startDelay = a.getInt(com.android.internal.R.styleable.Transition_startDelay, -1);
- if (startDelay > 0) {
- transition.setStartDelay(startDelay);
- }
- final int resID =
- a.getResourceId(com.android.internal.R.styleable.Animator_interpolator, 0);
- if (resID > 0) {
- transition.setInterpolator(AnimationUtils.loadInterpolator(mContext, resID));
- }
- String matchOrder =
- a.getString(com.android.internal.R.styleable.Transition_matchOrder);
- if (matchOrder != null) {
- transition.setMatchOrder(parseMatchOrder(matchOrder));
- }
- a.recycle();
- if (transition instanceof Visibility) {
- a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.VisibilityTransition);
- int mode = a.getInt(
- com.android.internal.R.styleable.VisibilityTransition_visibilityMode, 0);
- a.recycle();
- if (mode != 0) {
- ((Visibility)transition).setMode(mode);
- }
- }
- return transition;
- }
-
//
// TransitionManager loading
//
@@ -399,15 +315,11 @@ public class TransitionInflater {
private void loadTransition(AttributeSet attrs, ViewGroup sceneRoot,
TransitionManager transitionManager) throws Resources.NotFoundException {
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.TransitionManager);
- int transitionId = a.getResourceId(
- com.android.internal.R.styleable.TransitionManager_transition, -1);
- int fromId = a.getResourceId(
- com.android.internal.R.styleable.TransitionManager_fromScene, -1);
+ TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TransitionManager);
+ int transitionId = a.getResourceId(R.styleable.TransitionManager_transition, -1);
+ int fromId = a.getResourceId(R.styleable.TransitionManager_fromScene, -1);
Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
- int toId = a.getResourceId(
- com.android.internal.R.styleable.TransitionManager_toScene, -1);
+ int toId = a.getResourceId(R.styleable.TransitionManager_toScene, -1);
Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
if (transitionId >= 0) {
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 495814a..63957e9 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -16,9 +16,13 @@
package android.transition;
+import com.android.internal.R;
+
import android.animation.TimeInterpolator;
-import android.graphics.Rect;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.util.AndroidRuntimeException;
+import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -77,6 +81,15 @@ public class TransitionSet extends Transition {
public TransitionSet() {
}
+ public TransitionSet(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TransitionSet);
+ int ordering = a.getInt(R.styleable.TransitionSet_transitionOrdering,
+ TransitionSet.ORDERING_TOGETHER);
+ setOrdering(ordering);
+ a.recycle();
+ }
+
/**
* Sets the play order of this set's child transitions.
*
@@ -272,6 +285,14 @@ public class TransitionSet extends Transition {
return (TransitionSet) super.removeListener(listener);
}
+ @Override
+ public void setPathMotion(PathMotion pathMotion) {
+ super.setPathMotion(pathMotion);
+ for (int i = 0; i < mTransitions.size(); i++) {
+ mTransitions.get(i).setPathMotion(pathMotion);
+ }
+ }
+
/**
* Removes the specified child transition from this set.
*
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index aa9f04e..1629726 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -16,14 +16,18 @@
package android.transition;
+import com.android.internal.R;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-
/**
* This transition tracks changes to the visibility of target views in the
* start and end scenes. Visibility is determined not just by the
@@ -74,12 +78,25 @@ public abstract class Visibility extends Transition {
private int mMode = IN | OUT;
+ public Visibility() {}
+
+ public Visibility(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VisibilityTransition);
+ int mode = a.getInt(R.styleable.VisibilityTransition_visibilityMode, 0);
+ a.recycle();
+ if (mode != 0) {
+ setMode(mode);
+ }
+ }
+
/**
* Changes the transition to support appearing and/or disappearing Views, depending
* on <code>mode</code>.
*
* @param mode The behavior supported by this transition, a combination of
* {@link #IN} and {@link #OUT}.
+ * @attr ref android.R.styleable#VisibilityTransition_visibilityMode
*/
public void setMode(int mode) {
if ((mode & ~(IN | OUT)) != 0) {
@@ -88,6 +105,17 @@ public abstract class Visibility extends Transition {
mMode = mode;
}
+ /**
+ * Returns whether appearing and/or disappearing Views are supported.
+ *
+ * Returns whether appearing and/or disappearing Views are supported. A combination of
+ * {@link #IN} and {@link #OUT}.
+ * @attr ref android.R.styleable#VisibilityTransition_visibilityMode
+ */
+ public int getMode() {
+ return mMode;
+ }
+
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;