/*
* 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.state;
import androidx.annotation.NonNull;
import androidx.constraintlayout.core.motion.CustomVariable;
import androidx.constraintlayout.core.motion.Motion;
import androidx.constraintlayout.core.motion.MotionWidget;
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.utils.Easing;
import androidx.constraintlayout.core.motion.utils.KeyCache;
import androidx.constraintlayout.core.motion.utils.SpringStopEngine;
import androidx.constraintlayout.core.motion.utils.StopEngine;
import androidx.constraintlayout.core.motion.utils.StopLogicEngine;
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.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
public class Transition implements TypedValues {
private static final boolean DEBUG = false;
public static final int START = 0;
public static final int END = 1;
public static final int INTERPOLATED = 2;
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;
static final int ANTICIPATE = 6;
private static final int SPLINE_STRING = -1;
@SuppressWarnings("unused")
private static final int INTERPOLATOR_REFERENCE_ID = -2;
private HashMap<Integer, HashMap<String, KeyPosition>> mKeyPositions = new HashMap<>();
private HashMap<String, WidgetState> mState = new HashMap<>();
private TypedBundle mBundle = new TypedBundle();
// Interpolation
private int mDefaultInterpolator = 0;
private String mDefaultInterpolatorString = null;
private Easing mEasing = null;
private int mAutoTransition = 0;
private int mDuration = 400;
private float mStagger = 0.0f;
private OnSwipe mOnSwipe = null;
final CorePixelDp mToPixel; // Todo placed here as a temp till the refactor is done
int mParentStartWidth, mParentStartHeight;
int mParentEndWidth, mParentEndHeight;
int mParentInterpolatedWidth, mParentInterpolateHeight;
boolean mWrap;
public Transition(@NonNull CorePixelDp dpToPixel) {
mToPixel = dpToPixel;
}
// @TODO: add description
@SuppressWarnings("HiddenTypeParameter")
OnSwipe createOnSwipe() {
return mOnSwipe = new OnSwipe();
}
// @TODO: add description
public boolean hasOnSwipe() {
return mOnSwipe != null;
}
static class OnSwipe {
String mAnchorId;
private int mAnchorSide;
private StopEngine mEngine;
public static final int ANCHOR_SIDE_TOP = 0;
public static final int ANCHOR_SIDE_LEFT = 1;
public static final int ANCHOR_SIDE_RIGHT = 2;
public static final int ANCHOR_SIDE_BOTTOM = 3;
public static final int ANCHOR_SIDE_MIDDLE = 4;
public static final int ANCHOR_SIDE_START = 5;
public static final int ANCHOR_SIDE_END = 6;
public static final String[] SIDES = {"top", "left", "right",
"bottom", "middle", "start", "end"};
private static final float[][] TOUCH_SIDES = {
{0.5f, 0.0f}, // top
{0.0f, 0.5f}, // left
{1.0f, 0.5f}, // right
{0.5f, 1.0f}, // bottom
{0.5f, 0.5f}, // middle
{0.0f, 0.5f}, // start TODO (dynamically updated)
{1.0f, 0.5f}, // end TODO (dynamically updated)
};
@SuppressWarnings("unused")
private String mRotationCenterId;
@SuppressWarnings("unused")
private String mLimitBoundsTo;
@SuppressWarnings("unused")
private boolean mDragVertical = true;
private int mDragDirection = 0;
public static final int DRAG_UP = 0;
public static final int DRAG_DOWN = 1;
public static final int DRAG_LEFT = 2;
public static final int DRAG_RIGHT = 3;
public static final int DRAG_START = 4;
public static final int DRAG_END = 5;
public static final int DRAG_CLOCKWISE = 6;
public static final int DRAG_ANTICLOCKWISE = 7;
public static final String[] DIRECTIONS = {"up", "down", "left", "right", "start",
"end", "clockwise", "anticlockwise"};
private float mDragScale = 1;
@SuppressWarnings("unused")
private float mDragThreshold = 10;
private int mAutoCompleteMode = 0;
public static final int MODE_CONTINUOUS_VELOCITY = 0;
public static final int MODE_SPRING = 1;
public static final String[] MODE = {"velocity", "spring"};
private float mMaxVelocity = 4.f;
private float mMaxAcceleration = 1.2f;
// On touch up what happens
private int mOnTouchUp = 0;
public static final int ON_UP_AUTOCOMPLETE = 0;
public static final int ON_UP_AUTOCOMPLETE_TO_START = 1;
public static final int ON_UP_AUTOCOMPLETE_TO_END = 2;
public static final int ON_UP_STOP = 3;
public static final int ON_UP_DECELERATE = 4;
public static final int ON_UP_DECELERATE_AND_COMPLETE = 5;
public static final int ON_UP_NEVER_COMPLETE_TO_START = 6;
public static final int ON_UP_NEVER_COMPLETE_TO_END = 7;
public static final String[] TOUCH_UP = {"autocomplete", "toStart",
"toEnd", "stop", "decelerate", "decelerateComplete",
"neverCompleteStart", "neverCompleteEnd"};
private float mSpringMass = 1;
private float mSpringStiffness = 400;
private float mSpringDamping = 10;
private float mSpringStopThreshold = 0.01f;
private float mDestination = 0.0f;
// In spring mode what happens at the boundary
private int mSpringBoundary = 0;
public static final int BOUNDARY_OVERSHOOT = 0;
public static final int BOUNDARY_BOUNCE_START = 1;
public static final int BOUNDARY_BOUNCE_END = 2;
public static final int BOUNDARY_BOUNCE_BOTH = 3;
public static final String[] BOUNDARY = {"overshoot", "bounceStart",
"bounceEnd", "bounceBoth"};
private static final float[][] TOUCH_DIRECTION = {
{0.0f, -1.0f}, // up
{0.0f, 1.0f}, // down
{-1.0f, 0.0f}, // left
{1.0f, 0.0f}, // right
{-1.0f, 0.0f}, // start (dynamically updated)
{1.0f, 0.0f}, // end (dynamically updated)
};
private long mStart;
float getScale() {
return mDragScale;
}
float[] getDirection() {
return TOUCH_DIRECTION[mDragDirection];
}
float[] getSide() {
return TOUCH_SIDES[mAnchorSide];
}
void setAnchorId(String anchorId) {
this.mAnchorId = anchorId;
}
void setAnchorSide(int anchorSide) {
this.mAnchorSide = anchorSide;
}
void setRotationCenterId(String rotationCenterId) {
this.mRotationCenterId = rotationCenterId;
}
void setLimitBoundsTo(String limitBoundsTo) {
this.mLimitBoundsTo = limitBoundsTo;
}
void setDragDirection(int dragDirection) {
this.mDragDirection = dragDirection;
mDragVertical = (mDragDirection < 2);
}
void setDragScale(float dragScale) {
if (Float.isNaN(dragScale)) {
return;
}
this.mDragScale = dragScale;
}
void setDragThreshold(float dragThreshold) {
if (Float.isNaN(dragThreshold)) {
return;
}
this.mDragThreshold = dragThreshold;
}
void setAutoCompleteMode(int mAutoCompleteMode) {
this.mAutoCompleteMode = mAutoCompleteMode;
}
void setMaxVelocity(float maxVelocity) {
if (Float.isNaN(maxVelocity)) {
return;
}
this.mMaxVelocity = maxVelocity;
}
void setMaxAcceleration(float maxAcceleration) {
if (Float.isNaN(maxAcceleration)) {
return;
}
this.mMaxAcceleration = maxAcceleration;
}
void setOnTouchUp(int onTouchUp) {
this.mOnTouchUp = onTouchUp;
}
void setSpringMass(float mSpringMass) {
if (Float.isNaN(mSpringMass)) {
return;
}
this.mSpringMass = mSpringMass;
}
void setSpringStiffness(float mSpringStiffness) {
if (Float.isNaN(mSpringStiffness)) {
return;
}
this.mSpringStiffness = mSpringStiffness;
}
void setSpringDamping(float mSpringDamping) {
if (Float.isNaN(mSpringDamping)) {
return;
}
this.mSpringDamping = mSpringDamping;
}
void setSpringStopThreshold(float mSpringStopThreshold) {
if (Float.isNaN(mSpringStopThreshold)) {
return;
}
this.mSpringStopThreshold = mSpringStopThreshold;
}
void setSpringBoundary(int mSpringBoundary) {
this.mSpringBoundary = mSpringBoundary;
}
float getDestinationPosition(float currentPosition, float velocity, float duration) {
float rest = currentPosition + 0.5f * Math.abs(velocity) * velocity / mMaxAcceleration;
switch (mOnTouchUp) {
case ON_UP_AUTOCOMPLETE_TO_START:
case ON_UP_NEVER_COMPLETE_TO_END:
return 0;
case ON_UP_AUTOCOMPLETE_TO_END:
case ON_UP_NEVER_COMPLETE_TO_START:
return 1;
case ON_UP_STOP:
return Float.NaN;
case ON_UP_DECELERATE:
return Math.max(0, Math.min(1, rest));
case ON_UP_DECELERATE_AND_COMPLETE: // complete if within 20% of edge #todo improve
if (rest > 0.2f && rest < 0.8f) {
return rest;
} else {
return rest > .5f ? 1 : 0;
}
case ON_UP_AUTOCOMPLETE:
}
if (DEBUG) {
Utils.log(" currentPosition = " + currentPosition);
Utils.log(" velocity = " + velocity);
Utils.log(" peek = " + rest);
Utils.log("mMaxAcceleration = " + mMaxAcceleration);
}
return rest > .5 ? 1 : 0;
}
void config(float position, float velocity, long start, float duration) {
mStart = start;
mDestination = getDestinationPosition(position, velocity, duration);
if ((mOnTouchUp == ON_UP_DECELERATE)
&& (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY)) {
StopLogicEngine.Decelerate sld;
if (mEngine instanceof StopLogicEngine.Decelerate) {
sld = (StopLogicEngine.Decelerate) mEngine;
} else {
mEngine = sld = new StopLogicEngine.Decelerate();
}
sld.config(position, mDestination, velocity);
return;
}
if (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY) {
StopLogicEngine sl;
if (mEngine instanceof StopLogicEngine) {
sl = (StopLogicEngine) mEngine;
} else {
mEngine = sl = new StopLogicEngine();
}
sl.config(position, mDestination, velocity,
duration, mMaxAcceleration,
mMaxVelocity);
return;
}
SpringStopEngine sl;
if (mEngine instanceof SpringStopEngine) {
sl = (SpringStopEngine) mEngine;
} else {
mEngine = sl = new SpringStopEngine();
}
sl.springConfig(position, mDestination, velocity,
mSpringMass,
mSpringStiffness,
mSpringDamping,
mSpringStopThreshold, mSpringBoundary);
}
/**
* @param currentTime time in nanoseconds
* @return new values of progress
*/
public float getTouchUpProgress(long currentTime) {
float time = (currentTime - mStart) * 1E-9f;
float pos = mEngine.getInterpolation(time);
if (mEngine.isStopped()) {
pos = mDestination;
}
return pos;
}
public void printInfo() {
if (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY) {
System.out.println("velocity = " + mEngine.getVelocity());
System.out.println("mMaxAcceleration = " + mMaxAcceleration);
System.out.println("mMaxVelocity = " + mMaxVelocity);
} else {
System.out.println("mSpringMass = " + mSpringMass);
System.out.println("mSpringStiffness = " + mSpringStiffness);
System.out.println("mSpringDamping = " + mSpringDamping);
System.out.println("mSpringStopThreshold = " + mSpringStopThreshold);
System.out.println("mSpringBoundary = " + mSpringBoundary);
}
}
public boolean isNotDone(float progress) {
if (mOnTouchUp == ON_UP_STOP) {
return false;
}
return !mEngine.isStopped();
}
}
/**
* Converts from xy drag to progress
* This should be used till touch up
*
* @param baseW parent width
* @param baseH parent height
* @param dx change in x
* @param dy change in y
* @return the change in progress
*/
public float dragToProgress(float currentProgress, int baseW, int baseH, float dx, float dy) {
Collection<WidgetState> widgets = mState.values();
WidgetState childWidget = null;
for (WidgetState widget : widgets) {
childWidget = widget;
break;
}
if (mOnSwipe == null || childWidget == null) {
if (childWidget != null) {
return -dy / childWidget.mParentHeight;
}
return 1.0f;
}
if (mOnSwipe.mAnchorId == null) {
float[] dir = mOnSwipe.getDirection();
float motionDpDtX = childWidget.mParentHeight;
float motionDpDtY = childWidget.mParentHeight;
float drag = (dir[0] != 0) ? dx * Math.abs(dir[0]) / motionDpDtX
: dy * Math.abs(dir[1]) / motionDpDtY;
return drag * mOnSwipe.getScale();
}
WidgetState base = mState.get(mOnSwipe.mAnchorId);
float[] dir = mOnSwipe.getDirection();
float[] side = mOnSwipe.getSide();
float[] motionDpDt = new float[2];
base.interpolate(baseW, baseH, currentProgress, this);
base.mMotionControl.getDpDt(currentProgress, side[0], side[1], motionDpDt);
float drag = (dir[0] != 0) ? dx * Math.abs(dir[0]) / motionDpDt[0]
: dy * Math.abs(dir[1]) / motionDpDt[1];
if (DEBUG) {
Utils.log(" drag " + drag);
}
return drag * mOnSwipe.getScale();
}
/**
* Set the start of the touch up
*
* @param currentProgress 0...1 progress in
* @param currentTime time in nanoseconds
* @param velocityX pixels per millisecond
* @param velocityY pixels per millisecond
*/
public void setTouchUp(float currentProgress,
long currentTime,
float velocityX,
float velocityY) {
if (mOnSwipe != null) {
if (DEBUG) {
Utils.log(" >>> velocity x,y = " + velocityX + " , " + velocityY);
}
WidgetState base = mState.get(mOnSwipe.mAnchorId);
float[] motionDpDt = new float[2];
float[] dir = mOnSwipe.getDirection();
float[] side = mOnSwipe.getSide();
base.mMotionControl.getDpDt(currentProgress, side[0], side[1], motionDpDt);
float movementInDir = dir[0] * motionDpDt[0] + dir[1] * motionDpDt[1];
if (Math.abs(movementInDir) < 0.01) {
if (DEBUG) {
Utils.log(" >>> cap minimum v!! ");
}
motionDpDt[0] = .01f;
motionDpDt[1] = .01f;
}
float drag = (dir[0] != 0) ? velocityX / motionDpDt[0] : velocityY / motionDpDt[1];
drag *= mOnSwipe.getScale();
if (DEBUG) {
Utils.log(" >>> velocity " + drag);
Utils.log(" >>> mDuration " + mDuration);
Utils.log(" >>> currentProgress " + currentProgress);
}
mOnSwipe.config(currentProgress, drag, currentTime, mDuration * 1E-3f);
if (DEBUG) {
mOnSwipe.printInfo();
}
}
}
/**
* get the current touch up progress current time in nanoseconds
* (ideally coming from an animation clock)
*
* @param currentTime in nanoseconds
* @return progress
*/
public float getTouchUpProgress(long currentTime) {
if (mOnSwipe != null) {
return mOnSwipe.getTouchUpProgress(currentTime);
}
return 0;
}
/**
* Are we still animating
*
* @param currentProgress motion progress
* @return true to continue moving
*/
public boolean isTouchNotDone(float currentProgress) {
return mOnSwipe.isNotDone(currentProgress);
}
/**
* get the interpolater based on a constant or a string
*/
public static Interpolator getInterpolator(int interpolator, String interpolatorString) {
switch (interpolator) {
case SPLINE_STRING:
return v -> (float) Easing.getInterpolator(interpolatorString).get(v);
case EASE_IN_OUT:
return v -> (float) Easing.getInterpolator("standard").get(v);
case EASE_IN:
return v -> (float) Easing.getInterpolator("accelerate").get(v);
case EASE_OUT:
return v -> (float) Easing.getInterpolator("decelerate").get(v);
case LINEAR:
return v -> (float) Easing.getInterpolator("linear").get(v);
case ANTICIPATE:
return v -> (float) Easing.getInterpolator("anticipate").get(v);
case OVERSHOOT:
return v -> (float) Easing.getInterpolator("overshoot").get(v);
case BOUNCE: // TODO make a better bounce
return v -> (float) Easing.getInterpolator("spline(0.0, 0.2, 0.4, 0.6, "
+ "0.8 ,1.0, 0.8, 1.0, 0.9, 1.0)").get(v);
}
return null;
}
// @TODO: add description
@SuppressWarnings("HiddenTypeParameter")
public KeyPosition findPreviousPosition(String target, int frameNumber) {
while (frameNumber >= 0) {
HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber);
if (map != null) {
KeyPosition keyPosition = map.get(target);
if (keyPosition != null) {
return keyPosition;
}
}
frameNumber--;
}
return null;
}
// @TODO: add description
@SuppressWarnings("HiddenTypeParameter")
public KeyPosition findNextPosition(String target, int frameNumber) {
while (frameNumber <= 100) {
HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber);
if (map != null) {
KeyPosition keyPosition = map.get(target);
if (keyPosition != null) {
return keyPosition;
}
}
frameNumber++;
}
return null;
}
// @TODO: add description
public int getNumberKeyPositions(WidgetFrame frame) {
int numKeyPositions = 0;
int frameNumber = 0;
while (frameNumber <= 100) {
HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber);
if (map != null) {
KeyPosition keyPosition = map.get(frame.widget.stringId);
if (keyPosition != null) {
numKeyPositions++;
}
}
frameNumber++;
}
return numKeyPositions;
}
// @TODO: add description
public Motion getMotion(String id) {
return getWidgetState(id, null, 0).mMotionControl;
}
// @TODO: add description
public void fillKeyPositions(WidgetFrame frame, float[] x, float[] y, float[] pos) {
int numKeyPositions = 0;
int frameNumber = 0;
while (frameNumber <= 100) {
HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber);
if (map != null) {
KeyPosition keyPosition = map.get(frame.widget.stringId);
if (keyPosition != null) {
x[numKeyPositions] = keyPosition.mX;
y[numKeyPositions] = keyPosition.mY;
pos[numKeyPositions] = keyPosition.mFrame;
numKeyPositions++;
}
}
frameNumber++;
}
}
// @TODO: add description
public boolean hasPositionKeyframes() {
return mKeyPositions.size() > 0;
}
// @TODO: add description
public void setTransitionProperties(TypedBundle bundle) {
bundle.applyDelta(mBundle);
bundle.applyDelta(this);
}
@Override
public boolean setValue(int id, int value) {
return false;
}
@Override
public boolean setValue(int id, float value) {
if (id == TypedValues.TransitionType.TYPE_STAGGERED) {
mStagger = value;
}
return false;
}
@Override
public boolean setValue(int id, String value) {
if (id == TransitionType.TYPE_INTERPOLATOR) {
mEasing = Easing.getInterpolator(mDefaultInterpolatorString = value);
}
return false;
}
@Override
public boolean setValue(int id, boolean value) {
return false;
}
@Override
public int getId(String name) {
return 0;
}
public boolean isEmpty() {
return mState.isEmpty();
}
// @TODO: add description
public void clear() {
mState.clear();
}
// @TODO: add description
public boolean contains(String key) {
return mState.containsKey(key);
}
// @TODO: add description
public void addKeyPosition(String target, TypedBundle bundle) {
getWidgetState(target, null, 0).setKeyPosition(bundle);
}
// @TODO: add description
public void addKeyAttribute(String target, TypedBundle bundle) {
getWidgetState(target, null, 0).setKeyAttribute(bundle);
}
/**
* Add a key attribute and the custom variables into the
* @param target the id of the target
* @param bundle the key attributes bundle containing position etc.
* @param custom the customVariables to add at that position
*/
public void addKeyAttribute(String target, TypedBundle bundle, CustomVariable[]custom) {
getWidgetState(target, null, 0).setKeyAttribute(bundle,custom);
}
// @TODO: add description
public void addKeyCycle(String target, TypedBundle bundle) {
getWidgetState(target, null, 0).setKeyCycle(bundle);
}
// @TODO: add description
public void addKeyPosition(String target, int frame, int type, float x, float y) {
TypedBundle bundle = new TypedBundle();
bundle.add(TypedValues.PositionType.TYPE_POSITION_TYPE, 2);
bundle.add(TypedValues.TYPE_FRAME_POSITION, frame);
bundle.add(TypedValues.PositionType.TYPE_PERCENT_X, x);
bundle.add(TypedValues.PositionType.TYPE_PERCENT_Y, y);
getWidgetState(target, null, 0).setKeyPosition(bundle);
KeyPosition keyPosition = new KeyPosition(target, frame, type, x, y);
HashMap<String, KeyPosition> map = mKeyPositions.get(frame);
if (map == null) {
map = new HashMap<>();
mKeyPositions.put(frame, map);
}
map.put(target, keyPosition);
}
// @TODO: add description
public void addCustomFloat(int state, String widgetId, String property, float value) {
WidgetState widgetState = getWidgetState(widgetId, null, state);
WidgetFrame frame = widgetState.getFrame(state);
frame.addCustomFloat(property, value);
}
// @TODO: add description
public void addCustomColor(int state, String widgetId, String property, int color) {
WidgetState widgetState = getWidgetState(widgetId, null, state);
WidgetFrame frame = widgetState.getFrame(state);
frame.addCustomColor(property, color);
}
private void calculateParentDimensions(float progress) {
mParentInterpolatedWidth = (int) (0.5f +
mParentStartWidth + (mParentEndWidth - mParentStartWidth) * progress);
mParentInterpolateHeight = (int) (0.5f +
mParentStartHeight + (mParentEndHeight - mParentStartHeight) * progress);
}
public int getInterpolatedWidth() {
return mParentInterpolatedWidth;
}
public int getInterpolatedHeight() {
return mParentInterpolateHeight;
}
/**
* Update container of parameters for the state
*
* @param container contains all the widget parameters
* @param state starting or ending
*/
public void updateFrom(ConstraintWidgetContainer container, int state) {
mWrap = container.mListDimensionBehaviors[0]
== ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
mWrap |= container.mListDimensionBehaviors[1]
== ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
if (state == START) {
mParentInterpolatedWidth = mParentStartWidth = container.getWidth();
mParentInterpolateHeight = mParentStartHeight = container.getHeight();
} else {
mParentEndWidth = container.getWidth();
mParentEndHeight = container.getHeight();
}
final ArrayList<ConstraintWidget> children = container.getChildren();
final int count = children.size();
WidgetState[] states = new WidgetState[count];
for (int i = 0; i < count; i++) {
ConstraintWidget child = children.get(i);
WidgetState widgetState = getWidgetState(child.stringId, null, state);
states[i] = widgetState;
widgetState.update(child, state);
String id = widgetState.getPathRelativeId();
if (id != null) {
widgetState.setPathRelative(getWidgetState(id, null, state));
}
}
calcStagger();
}
// @TODO: add description
public void interpolate(int parentWidth, int parentHeight, float progress) {
if (mWrap) {
calculateParentDimensions(progress);
}
if (mEasing != null) {
progress = (float) mEasing.get(progress);
}
for (String key : mState.keySet()) {
WidgetState widget = mState.get(key);
widget.interpolate(parentWidth, parentHeight, progress, this);
}
}
// @TODO: add description
public WidgetFrame getStart(String id) {
WidgetState widgetState = mState.get(id);
if (widgetState == null) {
return null;
}
return widgetState.mStart;
}
// @TODO: add description
public WidgetFrame getEnd(String id) {
WidgetState widgetState = mState.get(id);
if (widgetState == null) {
return null;
}
return widgetState.mEnd;
}
// @TODO: add description
public WidgetFrame getInterpolated(String id) {
WidgetState widgetState = mState.get(id);
if (widgetState == null) {
return null;
}
return widgetState.mInterpolated;
}
// @TODO: add description
public float[] getPath(String id) {
WidgetState widgetState = mState.get(id);
int duration = 1000;
int frames = duration / 16;
float[] mPoints = new float[frames * 2];
widgetState.mMotionControl.buildPath(mPoints, frames);
return mPoints;
}
// @TODO: add description
public int getKeyFrames(String id, float[] rectangles, int[] pathMode, int[] position) {
WidgetState widgetState = mState.get(id);
return widgetState.mMotionControl.buildKeyFrames(rectangles, pathMode, position);
}
@SuppressWarnings("unused")
private WidgetState getWidgetState(String widgetId) {
return this.mState.get(widgetId);
}
public WidgetState getWidgetState(String widgetId,
ConstraintWidget child,
int transitionState) {
WidgetState widgetState = this.mState.get(widgetId);
if (widgetState == null) {
widgetState = new WidgetState();
mBundle.applyDelta(widgetState.mMotionControl);
widgetState.mMotionWidgetStart.updateMotion(widgetState.mMotionControl);
mState.put(widgetId, widgetState);
if (child != null) {
widgetState.update(child, transitionState);
}
}
return widgetState;
}
/**
* Used in debug draw
*/
public WidgetFrame getStart(ConstraintWidget child) {
return getWidgetState(child.stringId, null, Transition.START).mStart;
}
/**
* Used in debug draw
*/
public WidgetFrame getEnd(ConstraintWidget child) {
return getWidgetState(child.stringId, null, Transition.END).mEnd;
}
/**
* Used after the interpolation
*/
public WidgetFrame getInterpolated(ConstraintWidget child) {
return getWidgetState(child.stringId, null, Transition.INTERPOLATED).mInterpolated;
}
/**
* This gets the interpolator being used
*/
public Interpolator getInterpolator() {
return getInterpolator(mDefaultInterpolator, mDefaultInterpolatorString);
}
/**
* This gets the auto transition mode being used
*/
public int getAutoTransition() {
return mAutoTransition;
}
public static class WidgetState {
WidgetFrame mStart;
WidgetFrame mEnd;
WidgetFrame mInterpolated;
Motion mMotionControl;
boolean mNeedSetup = true;
MotionWidget mMotionWidgetStart;
MotionWidget mMotionWidgetEnd;
MotionWidget mMotionWidgetInterpolated;
KeyCache mKeyCache = new KeyCache();
int mParentHeight = -1;
int mParentWidth = -1;
public WidgetState() {
mStart = new WidgetFrame();
mEnd = new WidgetFrame();
mInterpolated = new WidgetFrame();
mMotionWidgetStart = new MotionWidget(mStart);
mMotionWidgetEnd = new MotionWidget(mEnd);
mMotionWidgetInterpolated = new MotionWidget(mInterpolated);
mMotionControl = new Motion(mMotionWidgetStart);
mMotionControl.setStart(mMotionWidgetStart);
mMotionControl.setEnd(mMotionWidgetEnd);
}
public void setKeyPosition(TypedBundle prop) {
MotionKeyPosition keyPosition = new MotionKeyPosition();
prop.applyDelta(keyPosition);
mMotionControl.addKey(keyPosition);
}
public void setKeyAttribute(TypedBundle prop) {
MotionKeyAttributes keyAttributes = new MotionKeyAttributes();
prop.applyDelta(keyAttributes);
mMotionControl.addKey(keyAttributes);
}
/**
* Set tge keyAttribute bundle and associated custom attributes
* @param prop
* @param custom
*/
public void setKeyAttribute(TypedBundle prop, CustomVariable[] custom) {
MotionKeyAttributes keyAttributes = new MotionKeyAttributes();
prop.applyDelta(keyAttributes);
if (custom != null) {
for (int i = 0; i < custom.length; i++) {
keyAttributes.mCustom.put( custom[i].getName(), custom[i]);
}
}
mMotionControl.addKey(keyAttributes);
}
public void setKeyCycle(TypedBundle prop) {
MotionKeyCycle keyAttributes = new MotionKeyCycle();
prop.applyDelta(keyAttributes);
mMotionControl.addKey(keyAttributes);
}
public void update(ConstraintWidget child, int state) {
if (state == START) {
mStart.update(child);
mMotionWidgetStart.updateMotion(mMotionWidgetStart);
mMotionControl.setStart(mMotionWidgetStart);
mNeedSetup = true;
} else if (state == END) {
mEnd.update(child);
mMotionControl.setEnd(mMotionWidgetEnd);
mNeedSetup = true;
}
mParentWidth = -1;
}
/**
* Return the id of the widget to animate relative to
*
* @return id of widget or null
*/
String getPathRelativeId() {
return mMotionControl.getAnimateRelativeTo();
}
public WidgetFrame getFrame(int type) {
if (type == START) {
return mStart;
} else if (type == END) {
return mEnd;
}
return mInterpolated;
}
public void interpolate(int parentWidth,
int parentHeight,
float progress,
Transition transition) {
// TODO only update if parentHeight != mParentHeight || parentWidth != mParentWidth) {
mParentHeight = parentHeight;
mParentWidth = parentWidth;
if (mNeedSetup) {
mMotionControl.setup(parentWidth, parentHeight, 1, System.nanoTime());
mNeedSetup = false;
}
WidgetFrame.interpolate(parentWidth, parentHeight,
mInterpolated, mStart, mEnd, transition, progress);
mInterpolated.interpolatedPos = progress;
mMotionControl.interpolate(mMotionWidgetInterpolated,
progress, System.nanoTime(), mKeyCache);
}
public void setPathRelative(WidgetState widgetState) {
mMotionControl.setupRelative(widgetState.mMotionControl);
}
}
static class KeyPosition {
int mFrame;
String mTarget;
int mType;
float mX;
float mY;
KeyPosition(String target, int frame, int type, float x, float y) {
this.mTarget = target;
this.mFrame = frame;
this.mType = type;
this.mX = x;
this.mY = y;
}
}
public void calcStagger() {
if (mStagger == 0.0f) {
return;
}
boolean flip = mStagger < 0.0;
float stagger = Math.abs(mStagger);
float min = Float.MAX_VALUE, max = -Float.MAX_VALUE;
boolean useMotionStagger = false;
for (String widgetId : mState.keySet()) {
WidgetState widgetState = mState.get(widgetId);
Motion f = widgetState.mMotionControl;
if (!Float.isNaN(f.getMotionStagger())) {
useMotionStagger = true;
break;
}
}
if (useMotionStagger) {
for (String widgetId : mState.keySet()) {
WidgetState widgetState = mState.get(widgetId);
Motion f = widgetState.mMotionControl;
float widgetStagger = f.getMotionStagger();
if (!Float.isNaN(widgetStagger)) {
min = Math.min(min, widgetStagger);
max = Math.max(max, widgetStagger);
}
}
for (String widgetId : mState.keySet()) {
WidgetState widgetState = mState.get(widgetId);
Motion f = widgetState.mMotionControl;
float widgetStagger = f.getMotionStagger();
if (!Float.isNaN(widgetStagger)) {
float scale = 1 / (1 - stagger);
float offset = stagger - stagger * (widgetStagger - min) / (max - min);
if (flip) {
offset = stagger - stagger
* ((max - widgetStagger) / (max - min));
}
f.setStaggerScale(scale);
f.setStaggerOffset(offset);
}
}
} else {
for (String widgetId : mState.keySet()) {
WidgetState widgetState = mState.get(widgetId);
Motion f = widgetState.mMotionControl;
float x = f.getFinalX();
float y = f.getFinalY();
float widgetStagger = x + y;
min = Math.min(min, widgetStagger);
max = Math.max(max, widgetStagger);
}
for (String widgetId : mState.keySet()) {
WidgetState widgetState = mState.get(widgetId);
Motion f = widgetState.mMotionControl;
float x = f.getFinalX();
float y = f.getFinalY();
float widgetStagger = x + y;
float offset = stagger - stagger * (widgetStagger - min) / (max - min);
if (flip) {
offset = stagger - stagger
* ((max - widgetStagger) / (max - min));
}
float scale = 1 / (1 - stagger);
f.setStaggerScale(scale);
f.setStaggerOffset(offset);
}
}
}
}