compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'
Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-beta01 it located at Google repository (https://maven.google.com/)
Contains the picture of a view through a transition and is used to interpolate it.
During a transition every view has a MotionController which drives its position.
All parameter which affect a views motion are added to MotionController and then setup()
builds out the splines that control the view.
get the left most position of the widget at the start of the movement.
get the top most position of the widget at the start of the movement.
Positive is down.
get the left most position of the widget at the end of the movement.
get the top most position of the widget at the end of the movement.
Positive is down.
get the width of the widget at the start of the movement.
get the width of the widget at the start of the movement.
get the width of the widget at the end of the movement.
get the width of the widget at the end of the movement.
Returns the id of the view to move relative to.
The position at the start and then end will be viewed relative to this view
-1 is the return value if NOT in polar mode
Fills the array point with the center coordinates.
point[0] is filled with the
x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
1.0
This returns the differential with respect to the animation layout position (Progress)
of a point on the view (post layout effects are not computed)
Gets the keyFrames for the view controlled by this MotionController.
The info data structure is of the form
0 length if your are at index i the [i+len+1] is the next entry
1 type 1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
2 position
3 x location
4 y location
5
...
length
/*
* Copyright (C) 2021 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 androidx.constraintlayout.core.motion;
import static androidx.constraintlayout.core.motion.MotionWidget.UNSET;
import androidx.constraintlayout.core.motion.key.MotionConstraintSet;
import androidx.constraintlayout.core.motion.key.MotionKey;
import androidx.constraintlayout.core.motion.key.MotionKeyAttributes;
import androidx.constraintlayout.core.motion.key.MotionKeyCycle;
import androidx.constraintlayout.core.motion.key.MotionKeyPosition;
import androidx.constraintlayout.core.motion.key.MotionKeyTimeCycle;
import androidx.constraintlayout.core.motion.key.MotionKeyTrigger;
import androidx.constraintlayout.core.motion.utils.CurveFit;
import androidx.constraintlayout.core.motion.utils.DifferentialInterpolator;
import androidx.constraintlayout.core.motion.utils.Easing;
import androidx.constraintlayout.core.motion.utils.FloatRect;
import androidx.constraintlayout.core.motion.utils.KeyCache;
import androidx.constraintlayout.core.motion.utils.KeyCycleOscillator;
import androidx.constraintlayout.core.motion.utils.KeyFrameArray;
import androidx.constraintlayout.core.motion.utils.Rect;
import androidx.constraintlayout.core.motion.utils.SplineSet;
import androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet;
import androidx.constraintlayout.core.motion.utils.TypedBundle;
import androidx.constraintlayout.core.motion.utils.TypedValues;
import androidx.constraintlayout.core.motion.utils.Utils;
import androidx.constraintlayout.core.motion.utils.VelocityMatrix;
import androidx.constraintlayout.core.motion.utils.ViewState;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
/**
* Contains the picture of a view through a transition and is used to interpolate it.
* During a transition every view has a MotionController which drives its position.
* <p>
* All parameter which affect a views motion are added to MotionController and then setup()
* builds out the splines that control the view.
*
*
*/
public class Motion implements TypedValues {
public static final int PATH_PERCENT = 0;
public static final int PATH_PERPENDICULAR = 1;
public static final int HORIZONTAL_PATH_X = 2;
public static final int HORIZONTAL_PATH_Y = 3;
public static final int VERTICAL_PATH_X = 4;
public static final int VERTICAL_PATH_Y = 5;
public static final int DRAW_PATH_NONE = 0;
public static final int DRAW_PATH_BASIC = 1;
public static final int DRAW_PATH_RELATIVE = 2;
public static final int DRAW_PATH_CARTESIAN = 3;
public static final int DRAW_PATH_AS_CONFIGURED = 4;
public static final int DRAW_PATH_RECTANGLE = 5;
public static final int DRAW_PATH_SCREEN = 6;
public static final int ROTATION_RIGHT = 1;
public static final int ROTATION_LEFT = 2;
Rect mTempRect = new Rect(); // for efficiency
private static final String TAG = "MotionController";
private static final boolean DEBUG = false;
private static final boolean FAVOR_FIXED_SIZE_VIEWS = false;
MotionWidget mView;
public String mId;
String mConstraintTag;
private int mCurveFitType = CurveFit.SPLINE;
private MotionPaths mStartMotionPath = new MotionPaths();
private MotionPaths mEndMotionPath = new MotionPaths();
private MotionConstrainedPoint mStartPoint = new MotionConstrainedPoint();
private MotionConstrainedPoint mEndPoint = new MotionConstrainedPoint();
// spline 0 is the generic one that process all the standard attributes
private CurveFit[] mSpline;
private CurveFit mArcSpline;
float mMotionStagger = Float.NaN;
float mStaggerOffset = 0;
float mStaggerScale = 1.0f;
float mCurrentCenterX, mCurrentCenterY;
private int[] mInterpolateVariables;
private double[] mInterpolateData; // scratch data created during setup
private double[] mInterpolateVelocity; // scratch data created during setup
private String[] mAttributeNames; // the names of the custom attributes
private int[] mAttributeInterpolatorCount; // how many interpolators for each custom attribute
private int mMaxDimension = 4;
private float[] mValuesBuff = new float[mMaxDimension];
private ArrayList<MotionPaths> mMotionPaths = new ArrayList<>();
private float[] mVelocity = new float[1]; // used as a temp buffer to return values
private ArrayList<MotionKey> mKeyList = new ArrayList<>(); // List of key frame items
// splines to calculate for use TimeCycles
private HashMap<String, TimeCycleSplineSet> mTimeCycleAttributesMap;
private HashMap<String, SplineSet> mAttributesMap; // splines to calculate values of attributes
// splines to calculate values of attributes
private HashMap<String, KeyCycleOscillator> mCycleMap;
private MotionKeyTrigger[] mKeyTriggers; // splines to calculate values of attributes
private int mPathMotionArc = UNSET;
// if set, pivot point is maintained as the other object
private int mTransformPivotTarget = UNSET;
// if set, pivot point is maintained as the other object
private MotionWidget mTransformPivotView = null;
private int mQuantizeMotionSteps = UNSET;
private float mQuantizeMotionPhase = Float.NaN;
private DifferentialInterpolator mQuantizeMotionInterpolator = null;
private boolean mNoMovement = false;
Motion mRelativeMotion;
/**
* Get the view to pivot around
*
* @return id of view or UNSET if not set
*/
public int getTransformPivotTarget() {
return mTransformPivotTarget;
}
/**
* Set a view to pivot around
*
* @param transformPivotTarget id of view
*/
public void setTransformPivotTarget(int transformPivotTarget) {
mTransformPivotTarget = transformPivotTarget;
mTransformPivotView = null;
}
/**
* provides access to MotionPath objects
*/
public MotionPaths getKeyFrame(int i) {
return mMotionPaths.get(i);
}
public Motion(MotionWidget view) {
setView(view);
}
/**
* get the left most position of the widget at the start of the movement.
*
* @return the left most position
*/
public float getStartX() {
return mStartMotionPath.mX;
}
/**
* get the top most position of the widget at the start of the movement.
* Positive is down.
*
* @return the top most position
*/
public float getStartY() {
return mStartMotionPath.mY;
}
/**
* get the left most position of the widget at the end of the movement.
*
* @return the left most position
*/
public float getFinalX() {
return mEndMotionPath.mX;
}
/**
* get the top most position of the widget at the end of the movement.
* Positive is down.
*
* @return the top most position
*/
public float getFinalY() {
return mEndMotionPath.mY;
}
/**
* get the width of the widget at the start of the movement.
*
* @return the width at the start
*/
public float getStartWidth() {
return mStartMotionPath.mWidth;
}
/**
* get the width of the widget at the start of the movement.
*
* @return the height at the start
*/
public float getStartHeight() {
return mStartMotionPath.mHeight;
}
/**
* get the width of the widget at the end of the movement.
*
* @return the width at the end
*/
public float getFinalWidth() {
return mEndMotionPath.mWidth;
}
/**
* get the width of the widget at the end of the movement.
*
* @return the height at the end
*/
public float getFinalHeight() {
return mEndMotionPath.mHeight;
}
/**
* Returns the id of the view to move relative to.
* The position at the start and then end will be viewed relative to this view
* -1 is the return value if NOT in polar mode
*
* @return the view id of the view this is in polar mode to or -1 if not in polar
*/
public String getAnimateRelativeTo() {
return mStartMotionPath.mAnimateRelativeTo;
}
/**
* set up the motion to be relative to this other motionController
*/
public void setupRelative(Motion motionController) {
mRelativeMotion = motionController;
}
private void setupRelative() {
if (mRelativeMotion == null) {
return;
}
mStartMotionPath.setupRelative(mRelativeMotion, mRelativeMotion.mStartMotionPath);
mEndMotionPath.setupRelative(mRelativeMotion, mRelativeMotion.mEndMotionPath);
}
public float getCenterX() {
return mCurrentCenterX;
}
public float getCenterY() {
return mCurrentCenterY;
}
// @TODO: add description
public void getCenter(double p, float[] pos, float[] vel) {
double[] position = new double[4];
double[] velocity = new double[4];
mSpline[0].getPos(p, position);
mSpline[0].getSlope(p, velocity);
Arrays.fill(vel, 0);
mStartMotionPath.getCenter(p, mInterpolateVariables, position, pos, velocity, vel);
}
/**
* Fills the array point with the center coordinates.
* point[0] is filled with the
* x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
* 1.0
*
* @param points array to fill (should be 2x the number of mPoints
*/
public void buildPath(float[] points, int pointCount) {
float mils = 1.0f / (pointCount - 1);
SplineSet trans_x =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
SplineSet trans_y =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
KeyCycleOscillator osc_x =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
KeyCycleOscillator osc_y =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
for (int i = 0; i < pointCount; i++) {
float position = i * mils;
if (mStaggerScale != 1.0f) {
if (position < mStaggerOffset) {
position = 0;
}
if (position > mStaggerOffset && position < 1.0) {
position -= mStaggerOffset;
position *= mStaggerScale;
position = Math.min(position, 1.0f);
}
}
double p = position;
Easing easing = mStartMotionPath.mKeyFrameEasing;
float start = 0;
float end = Float.NaN;
for (MotionPaths frame : mMotionPaths) {
if (frame.mKeyFrameEasing != null) { // this frame has an easing
if (frame.mTime < position) { // frame with easing is before the current pos
easing = frame.mKeyFrameEasing; // this is the candidate
start = frame.mTime; // this is also the starting time
} else { // frame with easing is past the pos
if (Float.isNaN(end)) { // we never ended the time line
end = frame.mTime;
}
}
}
}
if (easing != null) {
if (Float.isNaN(end)) {
end = 1.0f;
}
float offset = (position - start) / (end - start);
offset = (float) easing.get(offset);
p = offset * (end - start) + start;
}
mSpline[0].getPos(p, mInterpolateData);
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(p, mInterpolateData);
}
}
mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData, points, i * 2);
if (osc_x != null) {
points[i * 2] += osc_x.get(position);
} else if (trans_x != null) {
points[i * 2] += trans_x.get(position);
}
if (osc_y != null) {
points[i * 2 + 1] += osc_y.get(position);
} else if (trans_y != null) {
points[i * 2 + 1] += trans_y.get(position);
}
}
}
double[] getPos(double position) {
mSpline[0].getPos(position, mInterpolateData);
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(position, mInterpolateData);
}
}
return mInterpolateData;
}
/**
* Fills the array point with the center coordinates.
* point[0] is filled with the
* x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
* 1.0
*
* @param bounds array to fill (should be 2x the number of mPoints
* @return number of key frames
*/
void buildBounds(float[] bounds, int pointCount) {
float mils = 1.0f / (pointCount - 1);
@SuppressWarnings("unused") SplineSet trans_x =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
@SuppressWarnings("unused") SplineSet trans_y =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
@SuppressWarnings("unused") KeyCycleOscillator osc_x =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
@SuppressWarnings("unused") KeyCycleOscillator osc_y =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
for (int i = 0; i < pointCount; i++) {
float position = i * mils;
if (mStaggerScale != 1.0f) {
if (position < mStaggerOffset) {
position = 0;
}
if (position > mStaggerOffset && position < 1.0) {
position -= mStaggerOffset;
position *= mStaggerScale;
position = Math.min(position, 1.0f);
}
}
double p = position;
Easing easing = mStartMotionPath.mKeyFrameEasing;
float start = 0;
float end = Float.NaN;
for (MotionPaths frame : mMotionPaths) {
if (frame.mKeyFrameEasing != null) { // this frame has an easing
if (frame.mTime < position) { // frame with easing is before the current pos
easing = frame.mKeyFrameEasing; // this is the candidate
start = frame.mTime; // this is also the starting time
} else { // frame with easing is past the pos
if (Float.isNaN(end)) { // we never ended the time line
end = frame.mTime;
}
}
}
}
if (easing != null) {
if (Float.isNaN(end)) {
end = 1.0f;
}
float offset = (position - start) / (end - start);
offset = (float) easing.get(offset);
p = offset * (end - start) + start;
}
mSpline[0].getPos(p, mInterpolateData);
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(p, mInterpolateData);
}
}
mStartMotionPath.getBounds(mInterpolateVariables, mInterpolateData, bounds, i * 2);
}
}
private float getPreCycleDistance() {
int pointCount = 100;
float[] points = new float[2];
float sum = 0;
float mils = 1.0f / (pointCount - 1);
double x = 0, y = 0;
for (int i = 0; i < pointCount; i++) {
float position = i * mils;
double p = position;
Easing easing = mStartMotionPath.mKeyFrameEasing;
float start = 0;
float end = Float.NaN;
for (MotionPaths frame : mMotionPaths) {
if (frame.mKeyFrameEasing != null) { // this frame has an easing
if (frame.mTime < position) { // frame with easing is before the current pos
easing = frame.mKeyFrameEasing; // this is the candidate
start = frame.mTime; // this is also the starting time
} else { // frame with easing is past the pos
if (Float.isNaN(end)) { // we never ended the time line
end = frame.mTime;
}
}
}
}
if (easing != null) {
if (Float.isNaN(end)) {
end = 1.0f;
}
float offset = (position - start) / (end - start);
offset = (float) easing.get(offset);
p = offset * (end - start) + start;
}
mSpline[0].getPos(p, mInterpolateData);
mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData, points, 0);
if (i > 0) {
sum += (float) Math.hypot(y - points[1], x - points[0]);
}
x = points[0];
y = points[1];
}
return sum;
}
MotionKeyPosition getPositionKeyframe(int layoutWidth, int layoutHeight, float x, float y) {
FloatRect start = new FloatRect();
start.left = mStartMotionPath.mX;
start.top = mStartMotionPath.mY;
start.right = start.left + mStartMotionPath.mWidth;
start.bottom = start.top + mStartMotionPath.mHeight;
FloatRect end = new FloatRect();
end.left = mEndMotionPath.mX;
end.top = mEndMotionPath.mY;
end.right = end.left + mEndMotionPath.mWidth;
end.bottom = end.top + mEndMotionPath.mHeight;
for (MotionKey key : mKeyList) {
if (key instanceof MotionKeyPosition) {
if (((MotionKeyPosition) key).intersects(layoutWidth,
layoutHeight, start, end, x, y)) {
return (MotionKeyPosition) key;
}
}
}
return null;
}
// @TODO: add description
public int buildKeyFrames(float[] keyFrames, int[] mode, int[] pos) {
if (keyFrames != null) {
int count = 0;
double[] time = mSpline[0].getTimePoints();
if (mode != null) {
for (MotionPaths keyFrame : mMotionPaths) {
mode[count++] = keyFrame.mMode;
}
count = 0;
}
if (pos != null) {
for (MotionPaths keyFrame : mMotionPaths) {
pos[count++] = (int) (100 * keyFrame.mPosition);
}
count = 0;
}
for (int i = 0; i < time.length; i++) {
mSpline[0].getPos(time[i], mInterpolateData);
mStartMotionPath.getCenter(time[i],
mInterpolateVariables, mInterpolateData, keyFrames, count);
count += 2;
}
return count / 2;
}
return 0;
}
int buildKeyBounds(float[] keyBounds, int[] mode) {
if (keyBounds != null) {
int count = 0;
double[] time = mSpline[0].getTimePoints();
if (mode != null) {
for (MotionPaths keyFrame : mMotionPaths) {
mode[count++] = keyFrame.mMode;
}
count = 0;
}
for (int i = 0; i < time.length; i++) {
mSpline[0].getPos(time[i], mInterpolateData);
mStartMotionPath.getBounds(mInterpolateVariables,
mInterpolateData, keyBounds, count);
count += 2;
}
return count / 2;
}
return 0;
}
String[] mAttributeTable;
int getAttributeValues(String attributeType, float[] points, int pointCount) {
@SuppressWarnings("unused") float mils = 1.0f / (pointCount - 1);
SplineSet spline = mAttributesMap.get(attributeType);
if (spline == null) {
return -1;
}
for (int j = 0; j < points.length; j++) {
points[j] = spline.get(j / (points.length - 1));
}
return points.length;
}
// @TODO: add description
public void buildRect(float p, float[] path, int offset) {
p = getAdjustedPosition(p, null);
mSpline[0].getPos(p, mInterpolateData);
mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, offset);
}
void buildRectangles(float[] path, int pointCount) {
float mils = 1.0f / (pointCount - 1);
for (int i = 0; i < pointCount; i++) {
float position = i * mils;
position = getAdjustedPosition(position, null);
mSpline[0].getPos(position, mInterpolateData);
mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, i * 8);
}
}
float getKeyFrameParameter(int type, float x, float y) {
float dx = mEndMotionPath.mX - mStartMotionPath.mX;
float dy = mEndMotionPath.mY - mStartMotionPath.mY;
float startCenterX = mStartMotionPath.mX + mStartMotionPath.mWidth / 2;
float startCenterY = mStartMotionPath.mY + mStartMotionPath.mHeight / 2;
float hypotenuse = (float) Math.hypot(dx, dy);
if (hypotenuse < 0.0000001) {
return Float.NaN;
}
float vx = x - startCenterX;
float vy = y - startCenterY;
float distFromStart = (float) Math.hypot(vx, vy);
if (distFromStart == 0) {
return 0;
}
float pathDistance = (vx * dx + vy * dy);
switch (type) {
case PATH_PERCENT:
return pathDistance / hypotenuse;
case PATH_PERPENDICULAR:
return (float) Math.sqrt(hypotenuse * hypotenuse - pathDistance * pathDistance);
case HORIZONTAL_PATH_X:
return vx / dx;
case HORIZONTAL_PATH_Y:
return vy / dx;
case VERTICAL_PATH_X:
return vx / dy;
case VERTICAL_PATH_Y:
return vy / dy;
}
return 0;
}
private void insertKey(MotionPaths point) {
MotionPaths redundant = null;
for (MotionPaths p : mMotionPaths) {
if (point.mPosition == p.mPosition) {
redundant = p;
}
}
if (redundant != null) {
mMotionPaths.remove(redundant);
}
int pos = Collections.binarySearch(mMotionPaths, point);
if (pos == 0) {
Utils.loge(TAG, " KeyPath position \"" + point.mPosition + "\" outside of range");
}
mMotionPaths.add(-pos - 1, point);
}
void addKeys(ArrayList<MotionKey> list) {
mKeyList.addAll(list);
if (DEBUG) {
for (MotionKey key : mKeyList) {
Utils.log(TAG, " ################ set = " + key.getClass().getSimpleName());
}
}
}
// @TODO: add description
public void addKey(MotionKey key) {
mKeyList.add(key);
if (DEBUG) {
Utils.log(TAG, " ################ addKey = " + key.getClass().getSimpleName());
}
}
public void setPathMotionArc(int arc) {
mPathMotionArc = arc;
}
/**
* Called after all TimePoints & Cycles have been added;
* Spines are evaluated
*/
public void setup(int parentWidth,
int parentHeight,
float transitionDuration,
long currentTime) {
@SuppressWarnings({"unused", "ModifiedButNotUsed"})
HashSet<String> springAttributes = new HashSet<>();
// attributes we need to interpolate
HashSet<String> timeCycleAttributes = new HashSet<>(); // attributes we need to interpolate
HashSet<String> splineAttributes = new HashSet<>(); // attributes we need to interpolate
HashSet<String> cycleAttributes = new HashSet<>(); // attributes we need to oscillate
HashMap<String, Integer> interpolation = new HashMap<>();
ArrayList<MotionKeyTrigger> triggerList = null;
setupRelative();
if (DEBUG) {
if (mKeyList == null) {
Utils.log(TAG, ">>>>>>>>>>>>>>> mKeyList==null");
} else {
Utils.log(TAG, ">>>>>>>>>>>>>>> mKeyList for " + mView.getName());
}
}
if (mPathMotionArc != UNSET && mStartMotionPath.mPathMotionArc == UNSET) {
mStartMotionPath.mPathMotionArc = mPathMotionArc;
}
mStartPoint.different(mEndPoint, splineAttributes);
if (DEBUG) {
HashSet<String> attr = new HashSet<>();
mStartPoint.different(mEndPoint, attr);
Utils.log(TAG, ">>>>>>>>>>>>>>> MotionConstrainedPoint found "
+ Arrays.toString(attr.toArray()));
}
if (mKeyList != null) {
for (MotionKey key : mKeyList) {
if (key instanceof MotionKeyPosition) {
MotionKeyPosition keyPath = (MotionKeyPosition) key;
insertKey(new MotionPaths(parentWidth, parentHeight,
keyPath, mStartMotionPath, mEndMotionPath));
if (keyPath.mCurveFit != UNSET) {
mCurveFitType = keyPath.mCurveFit;
}
} else if (key instanceof MotionKeyCycle) {
key.getAttributeNames(cycleAttributes);
} else if (key instanceof MotionKeyTimeCycle) {
key.getAttributeNames(timeCycleAttributes);
} else if (key instanceof MotionKeyTrigger) {
if (triggerList == null) {
triggerList = new ArrayList<>();
}
triggerList.add((MotionKeyTrigger) key);
} else {
key.setInterpolation(interpolation);
key.getAttributeNames(splineAttributes);
}
}
}
//--------------------------- trigger support --------------------
if (triggerList != null) {
mKeyTriggers = triggerList.toArray(new MotionKeyTrigger[0]);
}
//--------------------------- splines support --------------------
if (!splineAttributes.isEmpty()) {
mAttributesMap = new HashMap<>();
for (String attribute : splineAttributes) {
SplineSet splineSets;
if (attribute.startsWith("CUSTOM,")) {
KeyFrameArray.CustomVar attrList = new KeyFrameArray.CustomVar();
String customAttributeName = attribute.split(",")[1];
for (MotionKey key : mKeyList) {
if (key.mCustom == null) {
continue;
}
CustomVariable customAttribute = key.mCustom.get(customAttributeName);
if (customAttribute != null) {
attrList.append(key.mFramePosition, customAttribute);
}
}
splineSets = SplineSet.makeCustomSplineSet(attribute, attrList);
} else {
splineSets = SplineSet.makeSpline(attribute, currentTime);
}
if (splineSets == null) {
continue;
}
splineSets.setType(attribute);
mAttributesMap.put(attribute, splineSets);
}
if (mKeyList != null) {
for (MotionKey key : mKeyList) {
if ((key instanceof MotionKeyAttributes)) {
key.addValues(mAttributesMap);
}
}
}
mStartPoint.addValues(mAttributesMap, 0);
mEndPoint.addValues(mAttributesMap, 100);
for (String spline : mAttributesMap.keySet()) {
int curve = CurveFit.SPLINE; // default is SPLINE
if (interpolation.containsKey(spline)) {
Integer boxedCurve = interpolation.get(spline);
if (boxedCurve != null) {
curve = boxedCurve;
}
}
SplineSet splineSet = mAttributesMap.get(spline);
if (splineSet != null) {
splineSet.setup(curve);
}
}
}
//--------------------------- timeCycle support --------------------
if (!timeCycleAttributes.isEmpty()) {
if (mTimeCycleAttributesMap == null) {
mTimeCycleAttributesMap = new HashMap<>();
}
for (String attribute : timeCycleAttributes) {
if (mTimeCycleAttributesMap.containsKey(attribute)) {
continue;
}
SplineSet splineSets = null;
if (attribute.startsWith("CUSTOM,")) {
KeyFrameArray.CustomVar attrList = new KeyFrameArray.CustomVar();
String customAttributeName = attribute.split(",")[1];
for (MotionKey key : mKeyList) {
if (key.mCustom == null) {
continue;
}
CustomVariable customAttribute = key.mCustom.get(customAttributeName);
if (customAttribute != null) {
attrList.append(key.mFramePosition, customAttribute);
}
}
splineSets = SplineSet.makeCustomSplineSet(attribute, attrList);
} else {
splineSets = SplineSet.makeSpline(attribute, currentTime);
}
if (splineSets == null) {
continue;
}
splineSets.setType(attribute);
// mTimeCycleAttributesMap.put(attribute, splineSets);
}
if (mKeyList != null) {
for (MotionKey key : mKeyList) {
if (key instanceof MotionKeyTimeCycle) {
((MotionKeyTimeCycle) key).addTimeValues(mTimeCycleAttributesMap);
}
}
}
for (String spline : mTimeCycleAttributesMap.keySet()) {
int curve = CurveFit.SPLINE; // default is SPLINE
if (interpolation.containsKey(spline)) {
curve = interpolation.get(spline);
}
mTimeCycleAttributesMap.get(spline).setup(curve);
}
}
//--------------------------------- end new key frame 2
MotionPaths[] points = new MotionPaths[2 + mMotionPaths.size()];
int count = 1;
points[0] = mStartMotionPath;
points[points.length - 1] = mEndMotionPath;
if (mMotionPaths.size() > 0 && mCurveFitType == MotionKey.UNSET) {
mCurveFitType = CurveFit.SPLINE;
}
for (MotionPaths point : mMotionPaths) {
points[count++] = point;
}
// ----- setup custom attributes which must be in the start and end constraint sets
int variables = 18;
HashSet<String> attributeNameSet = new HashSet<>();
for (String s : mEndMotionPath.mCustomAttributes.keySet()) {
if (mStartMotionPath.mCustomAttributes.containsKey(s)) {
if (!splineAttributes.contains("CUSTOM," + s)) {
attributeNameSet.add(s);
}
}
}
mAttributeNames = attributeNameSet.toArray(new String[0]);
mAttributeInterpolatorCount = new int[mAttributeNames.length];
for (int i = 0; i < mAttributeNames.length; i++) {
String attributeName = mAttributeNames[i];
mAttributeInterpolatorCount[i] = 0;
for (int j = 0; j < points.length; j++) {
if (points[j].mCustomAttributes.containsKey(attributeName)) {
CustomVariable attribute = points[j].mCustomAttributes.get(attributeName);
if (attribute != null) {
mAttributeInterpolatorCount[i] += attribute.numberOfInterpolatedValues();
break;
}
}
}
}
boolean arcMode = points[0].mPathMotionArc != UNSET;
boolean[] mask = new boolean[variables + mAttributeNames.length]; // defaults to false
for (int i = 1; i < points.length; i++) {
points[i].different(points[i - 1], mask, mAttributeNames, arcMode);
}
count = 0;
for (int i = 1; i < mask.length; i++) {
if (mask[i]) {
count++;
}
}
mInterpolateVariables = new int[count];
int varLen = Math.max(2, count);
mInterpolateData = new double[varLen];
mInterpolateVelocity = new double[varLen];
count = 0;
for (int i = 1; i < mask.length; i++) {
if (mask[i]) {
mInterpolateVariables[count++] = i;
}
}
double[][] splineData = new double[points.length][mInterpolateVariables.length];
double[] timePoint = new double[points.length];
for (int i = 0; i < points.length; i++) {
points[i].fillStandard(splineData[i], mInterpolateVariables);
timePoint[i] = points[i].mTime;
}
for (int j = 0; j < mInterpolateVariables.length; j++) {
int interpolateVariable = mInterpolateVariables[j];
if (interpolateVariable < MotionPaths.sNames.length) {
@SuppressWarnings("unused") String s =
MotionPaths.sNames[mInterpolateVariables[j]] + " [";
for (int i = 0; i < points.length; i++) {
s += splineData[i][j];
}
}
}
mSpline = new CurveFit[1 + mAttributeNames.length];
for (int i = 0; i < mAttributeNames.length; i++) {
int pointCount = 0;
double[][] splinePoints = null;
double[] timePoints = null;
String name = mAttributeNames[i];
for (int j = 0; j < points.length; j++) {
if (points[j].hasCustomData(name)) {
if (splinePoints == null) {
timePoints = new double[points.length];
splinePoints =
new double[points.length][points[j].getCustomDataCount(name)];
}
timePoints[pointCount] = points[j].mTime;
points[j].getCustomData(name, splinePoints[pointCount], 0);
pointCount++;
}
}
timePoints = Arrays.copyOf(timePoints, pointCount);
splinePoints = Arrays.copyOf(splinePoints, pointCount);
mSpline[i + 1] = CurveFit.get(mCurveFitType, timePoints, splinePoints);
}
// Spline for positions
mSpline[0] = CurveFit.get(mCurveFitType, timePoint, splineData);
// --------------------------- SUPPORT ARC MODE --------------
if (points[0].mPathMotionArc != UNSET) {
int size = points.length;
int[] mode = new int[size];
double[] time = new double[size];
double[][] values = new double[size][2];
for (int i = 0; i < size; i++) {
mode[i] = points[i].mPathMotionArc;
time[i] = points[i].mTime;
values[i][0] = points[i].mX;
values[i][1] = points[i].mY;
}
mArcSpline = CurveFit.getArc(mode, time, values);
}
//--------------------------- Cycle support --------------------
float distance = Float.NaN;
mCycleMap = new HashMap<>();
if (mKeyList != null) {
for (String attribute : cycleAttributes) {
KeyCycleOscillator cycle = KeyCycleOscillator.makeWidgetCycle(attribute);
if (cycle == null) {
continue;
}
if (cycle.variesByPath()) {
if (Float.isNaN(distance)) {
distance = getPreCycleDistance();
}
}
cycle.setType(attribute);
mCycleMap.put(attribute, cycle);
}
for (MotionKey key : mKeyList) {
if (key instanceof MotionKeyCycle) {
((MotionKeyCycle) key).addCycleValues(mCycleMap);
}
}
for (KeyCycleOscillator cycle : mCycleMap.values()) {
cycle.setup(distance);
}
}
if (DEBUG) {
Utils.log(TAG, "Animation of splineAttributes "
+ Arrays.toString(splineAttributes.toArray()));
Utils.log(TAG, "Animation of cycle " + Arrays.toString(mCycleMap.keySet().toArray()));
if (mAttributesMap != null) {
Utils.log(TAG, " splines = " + Arrays.toString(mAttributesMap.keySet().toArray()));
for (String s : mAttributesMap.keySet()) {
Utils.log(TAG, s + " = " + mAttributesMap.get(s));
}
}
Utils.log(TAG, " ---------------------------------------- ");
}
//--------------------------- end cycle support ----------------
}
/**
* Debug string
*/
@Override
public String toString() {
return " start: x: " + mStartMotionPath.mX + " y: " + mStartMotionPath.mY
+ " end: x: " + mEndMotionPath.mX + " y: " + mEndMotionPath.mY;
}
private void readView(MotionPaths motionPaths) {
motionPaths.setBounds((int) mView.getX(), (int) mView.getY(),
mView.getWidth(), mView.getHeight());
}
public void setView(MotionWidget view) {
mView = view;
}
public MotionWidget getView() {
return mView;
}
// @TODO: add description
public void setStart(MotionWidget mw) {
mStartMotionPath.mTime = 0;
mStartMotionPath.mPosition = 0;
mStartMotionPath.setBounds(mw.getX(), mw.getY(), mw.getWidth(), mw.getHeight());
mStartMotionPath.applyParameters(mw);
mStartPoint.setState(mw);
TypedBundle p = mw.getWidgetFrame().getMotionProperties();
if (p != null) {
p.applyDelta(this);
}
}
// @TODO: add description
public void setEnd(MotionWidget mw) {
mEndMotionPath.mTime = 1;
mEndMotionPath.mPosition = 1;
readView(mEndMotionPath);
mEndMotionPath.setBounds(mw.getLeft(), mw.getTop(), mw.getWidth(), mw.getHeight());
mEndMotionPath.applyParameters(mw);
mEndPoint.setState(mw);
}
// @TODO: add description
public void setStartState(ViewState rect,
MotionWidget v,
int rotation,
int preWidth,
int preHeight) {
mStartMotionPath.mTime = 0;
mStartMotionPath.mPosition = 0;
int cx, cy;
Rect r = new Rect();
switch (rotation) {
case 2:
cx = rect.left + rect.right;
cy = rect.top + rect.bottom;
r.left = preHeight - (cy + rect.width()) / 2;
r.top = (cx - rect.height()) / 2;
r.right = r.left + rect.width();
r.bottom = r.top + rect.height();
break;
case 1:
cx = rect.left + rect.right;
cy = rect.top + rect.bottom;
r.left = (cy - rect.width()) / 2;
r.top = preWidth - (cx + rect.height()) / 2;
r.right = r.left + rect.width();
r.bottom = r.top + rect.height();
break;
}
mStartMotionPath.setBounds(r.left, r.top, r.width(), r.height());
mStartPoint.setState(r, v, rotation, rect.rotation);
}
void rotate(Rect rect, Rect out, int rotation, int preHeight, int preWidth) {
int cx, cy;
switch (rotation) {
case MotionConstraintSet.ROTATE_PORTRATE_OF_LEFT:
cx = rect.left + rect.right;
cy = rect.top + rect.bottom;
out.left = preHeight - (cy + rect.width()) / 2;
out.top = (cx - rect.height()) / 2;
out.right = out.left + rect.width();
out.bottom = out.top + rect.height();
break;
case MotionConstraintSet.ROTATE_PORTRATE_OF_RIGHT:
cx = rect.left + rect.right;
cy = rect.top + rect.bottom;
out.left = (cy - rect.width()) / 2;
out.top = preWidth - (cx + rect.height()) / 2;
out.right = out.left + rect.width();
out.bottom = out.top + rect.height();
break;
case MotionConstraintSet.ROTATE_LEFT_OF_PORTRATE:
cx = rect.left + rect.right;
cy = rect.bottom + rect.top;
out.left = preHeight - (cy + rect.width()) / 2;
out.top = (cx - rect.height()) / 2;
out.right = out.left + rect.width();
out.bottom = out.top + rect.height();
break;
case MotionConstraintSet.ROTATE_RIGHT_OF_PORTRATE:
cx = rect.left + rect.right;
cy = rect.top + rect.bottom;
out.left = rect.height() / 2 + rect.top - cx / 2;
out.top = preWidth - (cx + rect.height()) / 2;
out.right = out.left + rect.width();
out.bottom = out.top + rect.height();
break;
}
}
// Todo : Implement QuantizeMotion scene rotate
// void setStartState(Rect cw, ConstraintSet constraintSet,
// int parentWidth, int parentHeight) {
// int rotate = constraintSet.mRotate; // for rotated frames
// if (rotate != 0) {
// rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
// }
// mStartMotionPath.time = 0;
// mStartMotionPath.position = 0;
// readView(mStartMotionPath);
// mStartMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
// ConstraintSet.Constraint constraint = constraintSet.getParameters(mId);
// mStartMotionPath.applyParameters(constraint);
// mMotionStagger = constraint.motion.mMotionStagger;
// mStartPoint.setState(cw, constraintSet, rotate, mId);
// mTransformPivotTarget = constraint.transform.transformPivotTarget;
// mQuantizeMotionSteps = constraint.motion.mQuantizeMotionSteps;
// mQuantizeMotionPhase = constraint.motion.mQuantizeMotionPhase;
// mQuantizeMotionInterpolator = getInterpolator(mView.getContext(),
// constraint.motion.mQuantizeInterpolatorType,
// constraint.motion.mQuantizeInterpolatorString,
// constraint.motion.mQuantizeInterpolatorID
// );
// }
static final int EASE_IN_OUT = 0;
static final int EASE_IN = 1;
static final int EASE_OUT = 2;
static final int LINEAR = 3;
static final int BOUNCE = 4;
static final int OVERSHOOT = 5;
private static final int SPLINE_STRING = -1;
@SuppressWarnings("unused") private static final int INTERPOLATOR_REFERENCE_ID = -2;
@SuppressWarnings("unused") private static final int INTERPOLATOR_UNDEFINED = -3;
private static DifferentialInterpolator getInterpolator(int type,
String interpolatorString,
@SuppressWarnings("unused") int id) {
switch (type) {
case SPLINE_STRING:
final Easing easing = Easing.getInterpolator(interpolatorString);
return new DifferentialInterpolator() {
float mX;
@Override
public float getInterpolation(float x) {
mX = x;
return (float) easing.get(x);
}
@Override
public float getVelocity() {
return (float) easing.getDiff(mX);
}
};
}
return null;
}
// void setEndState(Rect cw, ConstraintSet constraintSet, int parentWidth, int parentHeight) {
// int rotate = constraintSet.mRotate; // for rotated frames
// if (rotate != 0) {
// rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
// cw = mTempRect;
// }
// mEndMotionPath.time = 1;
// mEndMotionPath.position = 1;
// readView(mEndMotionPath);
// mEndMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
// mEndMotionPath.applyParameters(constraintSet.getParameters(mId));
// mEndPoint.setState(cw, constraintSet, rotate, mId);
// }
void setBothStates(MotionWidget v) {
mStartMotionPath.mTime = 0;
mStartMotionPath.mPosition = 0;
mNoMovement = true;
mStartMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
mEndMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
mStartPoint.setState(v);
mEndPoint.setState(v);
}
/**
* Calculates the adjusted position (and optional velocity).
* Note if requesting velocity staggering is not considered
*
* @param position position pre stagger
* @param velocity return velocity
* @return actual position accounting for easing and staggering
*/
private float getAdjustedPosition(float position, float[] velocity) {
if (velocity != null) {
velocity[0] = 1;
} else if (mStaggerScale != 1.0) {
if (position < mStaggerOffset) {
position = 0;
}
if (position > mStaggerOffset && position < 1.0) {
position -= mStaggerOffset;
position *= mStaggerScale;
position = Math.min(position, 1.0f);
}
}
// adjust the position based on the easing curve
float adjusted = position;
Easing easing = mStartMotionPath.mKeyFrameEasing;
float start = 0;
float end = Float.NaN;
for (MotionPaths frame : mMotionPaths) {
if (frame.mKeyFrameEasing != null) { // this frame has an easing
if (frame.mTime < position) { // frame with easing is before the current pos
easing = frame.mKeyFrameEasing; // this is the candidate
start = frame.mTime; // this is also the starting time
} else { // frame with easing is past the pos
if (Float.isNaN(end)) { // we never ended the time line
end = frame.mTime;
}
}
}
}
if (easing != null) {
if (Float.isNaN(end)) {
end = 1.0f;
}
float offset = (position - start) / (end - start);
float new_offset = (float) easing.get(offset);
adjusted = new_offset * (end - start) + start;
if (velocity != null) {
velocity[0] = (float) easing.getDiff(offset);
}
}
return adjusted;
}
void endTrigger(boolean start) {
// if ("button".equals(Debug.getName(mView)))
// if (mKeyTriggers != null) {
// for (int i = 0; i < mKeyTriggers.length; i++) {
// mKeyTriggers[i].conditionallyFire(start ? -100 : 100, mView);
// }
// }
}
//##############################################################################################
//$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
//$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
//$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
//$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%$%
//##############################################################################################
/**
* The main driver of interpolation
*
* @return do you need to keep animating
*/
public boolean interpolate(MotionWidget child,
float globalPosition,
long time,
KeyCache keyCache) {
@SuppressWarnings("unused") boolean timeAnimation = false;
float position = getAdjustedPosition(globalPosition, null);
// This quantize the position into steps e.g. 4 steps = 0-0.25,0.25-0.50 etc
if (mQuantizeMotionSteps != UNSET) {
@SuppressWarnings("unused") float pin = position;
float steps = 1.0f / mQuantizeMotionSteps; // the length of a step
float jump = (float) Math.floor(position / steps) * steps; // step jumps
float section = (position % steps) / steps; // float from 0 to 1 in a step
if (!Float.isNaN(mQuantizeMotionPhase)) {
section = (section + mQuantizeMotionPhase) % 1;
}
if (mQuantizeMotionInterpolator != null) {
section = mQuantizeMotionInterpolator.getInterpolation(section);
} else {
section = section > 0.5 ? 1 : 0;
}
position = section * steps + jump;
}
// MotionKeyTimeCycle.PathRotate timePathRotate = null;
if (mAttributesMap != null) {
for (SplineSet aSpline : mAttributesMap.values()) {
aSpline.setProperty(child, position);
}
}
// TODO add KeyTimeCycle
// if (mTimeCycleAttributesMap != null) {
// for (ViewTimeCycle aSpline : mTimeCycleAttributesMap.values()) {
// if (aSpline instanceof ViewTimeCycle.PathRotate) {
// timePathRotate = (ViewTimeCycle.PathRotate) aSpline;
// continue;
// }
// timeAnimation |= aSpline.setProperty(child, position, time, keyCache);
// }
// }
if (mSpline != null) {
mSpline[0].getPos(position, mInterpolateData);
mSpline[0].getSlope(position, mInterpolateVelocity);
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(position, mInterpolateData);
mArcSpline.getSlope(position, mInterpolateVelocity);
}
}
if (!mNoMovement) {
mStartMotionPath.setView(position, child,
mInterpolateVariables, mInterpolateData, mInterpolateVelocity, null);
}
if (mTransformPivotTarget != UNSET) {
if (mTransformPivotView == null) {
MotionWidget layout = (MotionWidget) child.getParent();
mTransformPivotView = layout.findViewById(mTransformPivotTarget);
}
if (mTransformPivotView != null) {
float cy =
(mTransformPivotView.getTop() + mTransformPivotView.getBottom()) / 2.0f;
float cx =
(mTransformPivotView.getLeft() + mTransformPivotView.getRight()) / 2.0f;
if (child.getRight() - child.getLeft() > 0
&& child.getBottom() - child.getTop() > 0) {
float px = (cx - child.getLeft());
float py = (cy - child.getTop());
child.setPivotX(px);
child.setPivotY(py);
}
}
}
// TODO add support for path rotate
// if (mAttributesMap != null) {
// for (SplineSet aSpline : mAttributesMap.values()) {
// if (aSpline instanceof ViewSpline.PathRotate
// && mInterpolateVelocity.length > 1)
// ((ViewSpline.PathRotate) aSpline).setPathRotate(child,
// position, mInterpolateVelocity[0], mInterpolateVelocity[1]);
// }
//
// }
// if (timePathRotate != null) {
// timeAnimation |= timePathRotate.setPathRotate(child, keyCache,
// position, time, mInterpolateVelocity[0], mInterpolateVelocity[1]);
// }
for (int i = 1; i < mSpline.length; i++) {
CurveFit spline = mSpline[i];
spline.getPos(position, mValuesBuff);
//interpolated here
mStartMotionPath.mCustomAttributes
.get(mAttributeNames[i - 1])
.setInterpolatedValue(child, mValuesBuff);
}
if (mStartPoint.mVisibilityMode == MotionWidget.VISIBILITY_MODE_NORMAL) {
if (position <= 0.0f) {
child.setVisibility(mStartPoint.mVisibility);
} else if (position >= 1.0f) {
child.setVisibility(mEndPoint.mVisibility);
} else if (mEndPoint.mVisibility != mStartPoint.mVisibility) {
child.setVisibility(MotionWidget.VISIBLE);
}
}
if (mKeyTriggers != null) {
for (int i = 0; i < mKeyTriggers.length; i++) {
mKeyTriggers[i].conditionallyFire(position, child);
}
}
} else {
// do the interpolation
float float_l =
(mStartMotionPath.mX + (mEndMotionPath.mX - mStartMotionPath.mX) * position);
float float_t =
(mStartMotionPath.mY + (mEndMotionPath.mY - mStartMotionPath.mY) * position);
float float_width = (mStartMotionPath.mWidth
+ (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
float float_height = (mStartMotionPath.mHeight
+ (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
int l = (int) (0.5f + float_l);
int t = (int) (0.5f + float_t);
int r = (int) (0.5f + float_l + float_width);
int b = (int) (0.5f + float_t + float_height);
int width = r - l;
int height = b - t;
if (FAVOR_FIXED_SIZE_VIEWS) {
l = (int) (mStartMotionPath.mX
+ (mEndMotionPath.mX - mStartMotionPath.mX) * position);
t = (int) (mStartMotionPath.mY
+ (mEndMotionPath.mY - mStartMotionPath.mY) * position);
width = (int) (mStartMotionPath.mWidth
+ (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
height = (int) (mStartMotionPath.mHeight
+ (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
r = l + width;
b = t + height;
}
// widget is responsible to call measure
child.layout(l, t, r, b);
}
// TODO add pathRotate KeyCycles
if (mCycleMap != null) {
for (KeyCycleOscillator osc : mCycleMap.values()) {
if (osc instanceof KeyCycleOscillator.PathRotateSet) {
((KeyCycleOscillator.PathRotateSet) osc).setPathRotate(child, position,
mInterpolateVelocity[0], mInterpolateVelocity[1]);
} else {
osc.setProperty(child, position);
}
}
}
// When we support TimeCycle return true if repaint is needed
// return timeAnimation;
return false;
}
/**
* This returns the differential with respect to the animation layout position (Progress)
* of a point on the view (post layout effects are not computed)
*
* @param position position in time
* @param locationX the x location on the view (0 = left edge, 1 = right edge)
* @param locationY the y location on the view (0 = top, 1 = bottom)
* @param mAnchorDpDt returns the differential of the motion with respect to the position
*/
public void getDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt) {
position = getAdjustedPosition(position, mVelocity);
if (mSpline != null) {
mSpline[0].getSlope(position, mInterpolateVelocity);
mSpline[0].getPos(position, mInterpolateData);
float v = mVelocity[0];
for (int i = 0; i < mInterpolateVelocity.length; i++) {
mInterpolateVelocity[i] *= v;
}
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(position, mInterpolateData);
mArcSpline.getSlope(position, mInterpolateVelocity);
mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
}
return;
}
mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
return;
}
// do the interpolation
float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
float dRight = dleft + dWidth;
float dBottom = dTop + dHeight;
mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
}
/**
* This returns the differential with respect to the animation post layout transform
* of a point on the view
*
* @param position position in time
* @param width width of the view
* @param height height of the view
* @param locationX the x location on the view (0 = left edge, 1 = right edge)
* @param locationY the y location on the view (0 = top, 1 = bottom)
* @param mAnchorDpDt returns the differential of the motion with respect to the position
*/
void getPostLayoutDvDp(float position,
int width,
int height,
float locationX,
float locationY,
float[] mAnchorDpDt) {
if (DEBUG) {
Utils.log(TAG, " position= " + position
+ " location= " + locationX + " , " + locationY);
}
position = getAdjustedPosition(position, mVelocity);
SplineSet trans_x =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_X);
SplineSet trans_y =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.TRANSLATION_Y);
SplineSet rotation =
(mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.ROTATION);
SplineSet scale_x = (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.SCALE_X);
SplineSet scale_y = (mAttributesMap == null) ? null : mAttributesMap.get(MotionKey.SCALE_Y);
KeyCycleOscillator osc_x =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_X);
KeyCycleOscillator osc_y =
(mCycleMap == null) ? null : mCycleMap.get(MotionKey.TRANSLATION_Y);
KeyCycleOscillator osc_r = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.ROTATION);
KeyCycleOscillator osc_sx = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.SCALE_X);
KeyCycleOscillator osc_sy = (mCycleMap == null) ? null : mCycleMap.get(MotionKey.SCALE_Y);
VelocityMatrix vmat = new VelocityMatrix();
vmat.clear();
vmat.setRotationVelocity(rotation, position);
vmat.setTranslationVelocity(trans_x, trans_y, position);
vmat.setScaleVelocity(scale_x, scale_y, position);
vmat.setRotationVelocity(osc_r, position);
vmat.setTranslationVelocity(osc_x, osc_y, position);
vmat.setScaleVelocity(osc_sx, osc_sy, position);
if (mArcSpline != null) {
if (mInterpolateData.length > 0) {
mArcSpline.getPos(position, mInterpolateData);
mArcSpline.getSlope(position, mInterpolateVelocity);
mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
}
vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
return;
}
if (mSpline != null) {
position = getAdjustedPosition(position, mVelocity);
mSpline[0].getSlope(position, mInterpolateVelocity);
mSpline[0].getPos(position, mInterpolateData);
float v = mVelocity[0];
for (int i = 0; i < mInterpolateVelocity.length; i++) {
mInterpolateVelocity[i] *= v;
}
mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
return;
}
// do the interpolation
float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
float dRight = dleft + dWidth;
float dBottom = dTop + dHeight;
mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
vmat.clear();
vmat.setRotationVelocity(rotation, position);
vmat.setTranslationVelocity(trans_x, trans_y, position);
vmat.setScaleVelocity(scale_x, scale_y, position);
vmat.setRotationVelocity(osc_r, position);
vmat.setTranslationVelocity(osc_x, osc_y, position);
vmat.setScaleVelocity(osc_sx, osc_sy, position);
vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
}
// @TODO: add description
public int getDrawPath() {
int mode = mStartMotionPath.mDrawPath;
for (MotionPaths keyFrame : mMotionPaths) {
mode = Math.max(mode, keyFrame.mDrawPath);
}
mode = Math.max(mode, mEndMotionPath.mDrawPath);
return mode;
}
public void setDrawPath(int debugMode) {
mStartMotionPath.mDrawPath = debugMode;
}
String name() {
return mView.getName();
}
void positionKeyframe(MotionWidget view,
MotionKeyPosition key,
float x,
float y,
String[] attribute,
float[] value) {
FloatRect start = new FloatRect();
start.left = mStartMotionPath.mX;
start.top = mStartMotionPath.mY;
start.right = start.left + mStartMotionPath.mWidth;
start.bottom = start.top + mStartMotionPath.mHeight;
FloatRect end = new FloatRect();
end.left = mEndMotionPath.mX;
end.top = mEndMotionPath.mY;
end.right = end.left + mEndMotionPath.mWidth;
end.bottom = end.top + mEndMotionPath.mHeight;
key.positionAttributes(view, start, end, x, y, attribute, value);
}
/**
* Get the keyFrames for the view controlled by this MotionController
*
* @param type is position(0-100) + 1000
* * mType(1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
* @param pos the x&y position of the keyFrame along the path
* @return Number of keyFrames found
*/
public int getKeyFramePositions(int[] type, float[] pos) {
int i = 0;
int count = 0;
for (MotionKey key : mKeyList) {
type[i++] = key.mFramePosition + 1000 * key.mType;
float time = key.mFramePosition / 100.0f;
mSpline[0].getPos(time, mInterpolateData);
mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, count);
count += 2;
}
return i;
}
/**
* Gets the keyFrames for the view controlled by this MotionController.
* The info data structure is of the form
* 0 length if your are at index i the [i+len+1] is the next entry
* 1 type 1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
* 2 position
* 3 x location
* 4 y location
* 5
* ...
* length
*
* @param info is a data structure array of int that holds info on each keyframe
* @return Number of keyFrames found
*/
public int getKeyFrameInfo(int type, int[] info) {
int count = 0;
int cursor = 0;
float[] pos = new float[2];
int len;
for (MotionKey key : mKeyList) {
if (key.mType != type && type == -1) {
continue;
}
len = cursor;
info[cursor] = 0;
info[++cursor] = key.mType;
info[++cursor] = key.mFramePosition;
float time = key.mFramePosition / 100.0f;
mSpline[0].getPos(time, mInterpolateData);
mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, 0);
info[++cursor] = Float.floatToIntBits(pos[0]);
info[++cursor] = Float.floatToIntBits(pos[1]);
if (key instanceof MotionKeyPosition) {
MotionKeyPosition kp = (MotionKeyPosition) key;
info[++cursor] = kp.mPositionType;
info[++cursor] = Float.floatToIntBits(kp.mPercentX);
info[++cursor] = Float.floatToIntBits(kp.mPercentY);
}
cursor++;
info[len] = cursor - len;
count++;
}
return count;
}
@Override
public boolean setValue(int id, int value) {
switch (id) {
case TypedValues.PositionType.TYPE_PATH_MOTION_ARC:
setPathMotionArc(value);
return true;
case TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS:
mQuantizeMotionSteps = value;
return true;
case TypedValues.TransitionType.TYPE_AUTO_TRANSITION:
// TODO add support for auto transitions mAutoTransition = value;
return true;
}
return false;
}
@Override
public boolean setValue(int id, float value) {
if (MotionType.TYPE_QUANTIZE_MOTION_PHASE == id) {
mQuantizeMotionPhase = value;
return true;
}
if (MotionType.TYPE_STAGGER == id) {
mMotionStagger = value;
return true;
}
return false;
}
@Override
public boolean setValue(int id, String value) {
if (TransitionType.TYPE_INTERPOLATOR == id
|| MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE == id) {
mQuantizeMotionInterpolator = getInterpolator(SPLINE_STRING, value, 0);
return true;
}
if (MotionType.TYPE_ANIMATE_RELATIVE_TO == id) {
mStartMotionPath.mAnimateRelativeTo = value;
return true;
}
return false;
}
@Override
public boolean setValue(int id, boolean value) {
return false;
}
@Override
public int getId(String name) {
return 0;
}
/**
* Set stagger scale
*/
public void setStaggerScale(float staggerScale) {
mStaggerScale = staggerScale;
}
/**
* set the offset used in calculating stagger launches
*
* @param staggerOffset fraction of progress before this controller runs
*/
public void setStaggerOffset(float staggerOffset) {
mStaggerOffset = staggerOffset;
}
/**
* The values set in
* motion: {
* stagger: '2'
* }
*
* @return value from motion: { stagger: ? } or NaN if not set
*/
public float getMotionStagger() {
return mMotionStagger;
}
public void setIdString(String stringId) {
mId = stringId;
mStartMotionPath.mId = mId;
}
}