Add curved motion to Transitions.

Bug 15197527

Added two public PathMotions: PatternMotion and ArcMotion.
ArcMotion is the algorithm provided by UX. PatternMotion
provides a mechanism for using a Path as a pattern.

Change-Id: Ie57fd5f4e62269acc1164eced39853a12c52bd77
This commit is contained in:
George Mount
2014-06-19 07:51:08 -07:00
parent 32292eb87b
commit ecd857be39
20 changed files with 988 additions and 222 deletions

View File

@@ -825,6 +825,7 @@ package android {
field public static final int maxRows = 16843059; // 0x1010133
field public static final int maxSdkVersion = 16843377; // 0x1010271
field public static final int maxWidth = 16843039; // 0x101011f
field public static final int maximumAngle = 16843905; // 0x1010481
field public static final int measureAllChildren = 16843018; // 0x101010a
field public static final int measureWithLargestChild = 16843476; // 0x10102d4
field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad
@@ -840,6 +841,8 @@ package android {
field public static final int minResizeWidth = 16843669; // 0x1010395
field public static final int minSdkVersion = 16843276; // 0x101020c
field public static final int minWidth = 16843071; // 0x101013f
field public static final int minimumHorizontalAngle = 16843903; // 0x101047f
field public static final int minimumVerticalAngle = 16843904; // 0x1010480
field public static final int mipMap = 16843725; // 0x10103cd
field public static final int mirrorForRtl = 16843726; // 0x10103ce
field public static final int mode = 16843134; // 0x101017e
@@ -30450,12 +30453,26 @@ package android.text.util {
package android.transition {
public class ArcMotion extends android.transition.PathMotion {
ctor public ArcMotion();
ctor public ArcMotion(android.content.Context, android.util.AttributeSet);
method public float getMaximumAngle();
method public float getMinimumHorizontalAngle();
method public float getMinimumVerticalAngle();
method public android.graphics.Path getPath(float, float, float, float);
method public void setMaximumAngle(float);
method public void setMinimumHorizontalAngle(float);
method public void setMinimumVerticalAngle(float);
}
public class AutoTransition extends android.transition.TransitionSet {
ctor public AutoTransition();
ctor public AutoTransition(android.content.Context, android.util.AttributeSet);
}
public class ChangeBounds extends android.transition.Transition {
ctor public ChangeBounds();
ctor public ChangeBounds(android.content.Context, android.util.AttributeSet);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
method public void setReparent(boolean);
@@ -30464,18 +30481,21 @@ package android.transition {
public class ChangeClipBounds extends android.transition.Transition {
ctor public ChangeClipBounds();
ctor public ChangeClipBounds(android.content.Context, android.util.AttributeSet);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
}
public class ChangeImageTransform extends android.transition.Transition {
ctor public ChangeImageTransform();
ctor public ChangeImageTransform(android.content.Context, android.util.AttributeSet);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
}
public class ChangeTransform extends android.transition.Transition {
ctor public ChangeTransform();
ctor public ChangeTransform(android.content.Context, android.util.AttributeSet);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
}
@@ -30488,15 +30508,32 @@ package android.transition {
public class Explode extends android.transition.Visibility {
ctor public Explode();
ctor public Explode(android.content.Context, android.util.AttributeSet);
}
public class Fade extends android.transition.Visibility {
ctor public Fade();
ctor public Fade(int);
ctor public Fade(android.content.Context, android.util.AttributeSet);
field public static final int IN = 1; // 0x1
field public static final int OUT = 2; // 0x2
}
public abstract class PathMotion {
ctor public PathMotion();
ctor public PathMotion(android.content.Context, android.util.AttributeSet);
method public abstract android.graphics.Path getPath(float, float, float, float);
}
public class PatternMotion extends android.transition.PathMotion {
ctor public PatternMotion();
ctor public PatternMotion(android.content.Context, android.util.AttributeSet);
ctor public PatternMotion(android.graphics.Path);
method public android.graphics.Path getPath(float, float, float, float);
method public android.graphics.Path getPattern();
method public void setPattern(android.graphics.Path);
}
public final class Scene {
ctor public Scene(android.view.ViewGroup);
ctor public Scene(android.view.ViewGroup, android.view.View);
@@ -30519,11 +30556,14 @@ package android.transition {
public class Slide extends android.transition.Visibility {
ctor public Slide();
ctor public Slide(int);
ctor public Slide(android.content.Context, android.util.AttributeSet);
method public int getSlideEdge();
method public void setSlideEdge(int);
}
public abstract class Transition implements java.lang.Cloneable {
ctor public Transition();
ctor public Transition(android.content.Context, android.util.AttributeSet);
method public android.transition.Transition addListener(android.transition.Transition.TransitionListener);
method public android.transition.Transition addTarget(int);
method public android.transition.Transition addTarget(java.lang.String);
@@ -30546,6 +30586,7 @@ package android.transition {
method public android.transition.Transition.EpicenterCallback getEpicenterCallback();
method public android.animation.TimeInterpolator getInterpolator();
method public java.lang.String getName();
method public android.transition.PathMotion getPathMotion();
method public android.transition.TransitionPropagation getPropagation();
method public long getStartDelay();
method public java.util.List<java.lang.Integer> getTargetIds();
@@ -30563,6 +30604,7 @@ package android.transition {
method public void setEpicenterCallback(android.transition.Transition.EpicenterCallback);
method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator);
method public void setMatchOrder(int...);
method public void setPathMotion(android.transition.PathMotion);
method public void setPropagation(android.transition.TransitionPropagation);
method public android.transition.Transition setStartDelay(long);
field public static final int MATCH_ID = 3; // 0x3
@@ -30610,6 +30652,7 @@ package android.transition {
public class TransitionSet extends android.transition.Transition {
ctor public TransitionSet();
ctor public TransitionSet(android.content.Context, android.util.AttributeSet);
method public android.transition.TransitionSet addTransition(android.transition.Transition);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
@@ -30628,8 +30671,10 @@ package android.transition {
public abstract class Visibility extends android.transition.Transition {
ctor public Visibility();
ctor public Visibility(android.content.Context, android.util.AttributeSet);
method public void captureEndValues(android.transition.TransitionValues);
method public void captureStartValues(android.transition.TransitionValues);
method public int getMode();
method public boolean isVisible(android.transition.TransitionValues);
method public android.animation.Animator onAppear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
method public android.animation.Animator onAppear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);

View File

@@ -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;
}
}

View File

@@ -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()).

View File

@@ -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);
ObjectAnimator topLeftAnimator = null;
if (startLeft != endLeft || startTop != endTop) {
Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
endLeft, endTop);
topLeftAnimator = ObjectAnimator.ofInt(view, "left", "top", topLeftPath);
}
if (startTop != endTop) {
pvh[pvhIndex++] = PropertyValuesHolder.ofInt("top", startTop, endTop);
ObjectAnimator bottomRightAnimator = null;
if (startRight != endRight || startBottom != endBottom) {
Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
endRight, endBottom);
bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
bottomRightPath);
}
if (startRight != endRight) {
pvh[pvhIndex++] = PropertyValuesHolder.ofInt("right",
startRight, endRight);
}
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;
}
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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
@@ -223,6 +253,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
@@ -1839,6 +1929,36 @@ public abstract class Transition implements Cloneable {
return mEpicenterCallback.onGetEpicenter(this);
}
/**
* 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

View File

@@ -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,117 +130,105 @@ 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);
}
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);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not find transition class " + className, e);
}
}
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);
}
}
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;
return constructor.newInstance(mContext, attrs);
}
} catch (InstantiationException e) {
throw new InflateException("Could not instantiate " + expectedType + " class " +
className, e);
} catch (ClassNotFoundException 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 void getTargetIds(XmlPullParser parser,
@@ -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) {

View File

@@ -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.
*

View File

@@ -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;

View File

@@ -5429,6 +5429,28 @@
<attr name="toScene" format="reference" />
</declare-styleable>
<!-- Use <code>arcMotion</code> as the root tag of the XML resource that
describes a {@link android.transition.ArcMotion}. This must be used
within a transition with which the PathMotion should be associated. -->
<declare-styleable name="ArcMotion">
<!-- The minimum arc angle in degrees between the start and end points when
they are close to horizontal. -->
<attr name="minimumHorizontalAngle" format="float" />
<!-- The minimum arc angle in degrees between the start and end points when
they are close to vertical. -->
<attr name="minimumVerticalAngle" format="float" />
<!-- The maximum arc angle in degrees between the start and end points. -->
<attr name="maximumAngle" format="float" />
</declare-styleable>
<!-- Use <code>patternMotion</code> as the root tag of the XML resource that
describes a {@link android.transition.PatternMotion}. This must be used
within a transition with which the PathMotion should be associated. -->
<declare-styleable name="PatternMotion">
<!-- The path string describing the pattern to use for the PathMotion. -->
<attr name="pathData" />
</declare-styleable>
<!-- ========================== -->
<!-- ValueAnimator class attributes -->
<!-- ========================== -->

View File

@@ -2216,6 +2216,9 @@
<public type="attr" name="actionModeFindDrawable" />
<public type="attr" name="actionModeWebSearchDrawable" />
<public type="attr" name="visibilityMode" />
<public type="attr" name="minimumHorizontalAngle" />
<public type="attr" name="minimumVerticalAngle" />
<public type="attr" name="maximumAngle" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />