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/)
For the given position (in the MotionLayout coordinate space) determine whether we accept
the first down for on swipe.
This is based off . If null, we accept the drag at any
position, otherwise, we only accept it if it's within its bounds.
get the current touch up progress current time in nanoseconds
(ideally coming from an animation clock)
/*
* 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.annotation.RestrictTo;
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;
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:
if (currentPosition >= 1f) {
return 1;
}
return 0;
case ON_UP_NEVER_COMPLETE_TO_END:
return 0;
case ON_UP_AUTOCOMPLETE_TO_END:
if (currentPosition <= 0f) {
return 0;
}
return 1;
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;
if (Math.abs(velocity) > mMaxVelocity) {
velocity = mMaxVelocity * Math.signum(velocity);
}
mDestination = getDestinationPosition(position, velocity, duration);
if (mDestination == position) {
mEngine = null;
return;
}
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 != null && !mEngine.isStopped();
}
}
/**
* For the given position (in the MotionLayout coordinate space) determine whether we accept
* the first down for on swipe.
* <p>
* This is based off {@link OnSwipe#mLimitBoundsTo}. If null, we accept the drag at any
* position, otherwise, we only accept it if it's within its bounds.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public boolean isFirstDownAccepted(float posX, float posY) {
if (mOnSwipe == null) {
return false;
}
if (mOnSwipe.mLimitBoundsTo != null) {
WidgetState targetWidget = mState.get(mOnSwipe.mLimitBoundsTo);
if (targetWidget == null) {
System.err.println("mLimitBoundsTo target is null");
return false;
}
// Calculate against the interpolated/current frame
WidgetFrame frame = targetWidget.getFrame(2);
return posX >= frame.left && posX < frame.right && posY >= frame.top
&& posY < frame.bottom;
} else {
return true;
}
}
/**
* 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();
}
/**
* Reset animation properties of the Transition.
* <p>
* This will not affect the internal model of the widgets (a.k.a. {@link #mState}).
*/
void resetProperties() {
mOnSwipe = null;
mBundle.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);
}
}
}
}