public class

Motion

extends java.lang.Object

implements TypedValues

 java.lang.Object

↳androidx.constraintlayout.core.motion.Motion

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: 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/)

Overview

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.

Summary

Fields
public static final intDRAW_PATH_AS_CONFIGURED

public static final intDRAW_PATH_BASIC

public static final intDRAW_PATH_CARTESIAN

public static final intDRAW_PATH_NONE

public static final intDRAW_PATH_RECTANGLE

public static final intDRAW_PATH_RELATIVE

public static final intDRAW_PATH_SCREEN

public static final intHORIZONTAL_PATH_X

public static final intHORIZONTAL_PATH_Y

public java.lang.StringmId

public static final intPATH_PERCENT

public static final intPATH_PERPENDICULAR

public static final intROTATION_LEFT

public static final intROTATION_RIGHT

public static final intVERTICAL_PATH_X

public static final intVERTICAL_PATH_Y

Constructors
publicMotion(MotionWidget view)

Methods
public voidaddKey(MotionKey key)

public intbuildKeyFrames(float[] keyFrames[], int[] mode[], int[] pos[])

public voidbuildPath(float[] points[], int pointCount)

Fills the array point with the center coordinates.

public voidbuildRect(float p, float[] path[], int offset)

public java.lang.StringgetAnimateRelativeTo()

Returns the id of the view to move relative to.

public voidgetCenter(double p, float[] pos[], float[] vel[])

public floatgetCenterX()

public floatgetCenterY()

public voidgetDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt[])

This returns the differential with respect to the animation layout position (Progress) of a point on the view (post layout effects are not computed)

public intgetDrawPath()

public floatgetFinalHeight()

get the width of the widget at the end of the movement.

public floatgetFinalWidth()

get the width of the widget at the end of the movement.

public floatgetFinalX()

get the left most position of the widget at the end of the movement.

public floatgetFinalY()

get the top most position of the widget at the end of the movement.

public intgetId(java.lang.String name)

public MotionPathsgetKeyFrame(int i)

provides access to MotionPath objects

public intgetKeyFrameInfo(int type, int[] info[])

Gets the keyFrames for the view controlled by this MotionController.

public intgetKeyFramePositions(int[] type[], float[] pos[])

Get the keyFrames for the view controlled by this MotionController

public floatgetMotionStagger()

The values set in motion: { stagger: '2' }

public floatgetStartHeight()

get the width of the widget at the start of the movement.

public floatgetStartWidth()

get the width of the widget at the start of the movement.

public floatgetStartX()

get the left most position of the widget at the start of the movement.

public floatgetStartY()

get the top most position of the widget at the start of the movement.

public intgetTransformPivotTarget()

Get the view to pivot around

public MotionWidgetgetView()

public booleaninterpolate(MotionWidget child, float globalPosition, long time, KeyCache keyCache)

The main driver of interpolation

public voidsetDrawPath(int debugMode)

public voidsetEnd(MotionWidget mw)

public voidsetIdString(java.lang.String stringId)

public voidsetPathMotionArc(int arc)

public voidsetStaggerOffset(float staggerOffset)

set the offset used in calculating stagger launches

public voidsetStaggerScale(float staggerScale)

Set stagger scale

public voidsetStart(MotionWidget mw)

public voidsetStartState(ViewState rect, MotionWidget v, int rotation, int preWidth, int preHeight)

public voidsetTransformPivotTarget(int transformPivotTarget)

Set a view to pivot around

public voidsetup(int parentWidth, int parentHeight, float transitionDuration, long currentTime)

Called after all TimePoints & Cycles have been added; Spines are evaluated

public voidsetupRelative(Motion motionController)

set up the motion to be relative to this other motionController

public booleansetValue(int id, boolean value)

public booleansetValue(int id, float value)

public booleansetValue(int id, int value)

public booleansetValue(int id, java.lang.String value)

public voidsetView(MotionWidget view)

public java.lang.StringtoString()

Debug string

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final int PATH_PERCENT

public static final int PATH_PERPENDICULAR

public static final int HORIZONTAL_PATH_X

public static final int HORIZONTAL_PATH_Y

public static final int VERTICAL_PATH_X

public static final int VERTICAL_PATH_Y

public static final int DRAW_PATH_NONE

public static final int DRAW_PATH_BASIC

public static final int DRAW_PATH_RELATIVE

public static final int DRAW_PATH_CARTESIAN

public static final int DRAW_PATH_AS_CONFIGURED

public static final int DRAW_PATH_RECTANGLE

public static final int DRAW_PATH_SCREEN

public static final int ROTATION_RIGHT

public static final int ROTATION_LEFT

public java.lang.String mId

Constructors

public Motion(MotionWidget view)

Methods

public int getTransformPivotTarget()

Get the view to pivot around

Returns:

id of view or UNSET if not set

public void setTransformPivotTarget(int transformPivotTarget)

Set a view to pivot around

Parameters:

transformPivotTarget: id of view

public MotionPaths getKeyFrame(int i)

provides access to MotionPath objects

public float getStartX()

get the left most position of the widget at the start of the movement.

Returns:

the left most position

public float getStartY()

get the top most position of the widget at the start of the movement. Positive is down.

Returns:

the top most position

public float getFinalX()

get the left most position of the widget at the end of the movement.

Returns:

the left most position

public float getFinalY()

get the top most position of the widget at the end of the movement. Positive is down.

Returns:

the top most position

public float getStartWidth()

get the width of the widget at the start of the movement.

Returns:

the width at the start

public float getStartHeight()

get the width of the widget at the start of the movement.

Returns:

the height at the start

public float getFinalWidth()

get the width of the widget at the end of the movement.

Returns:

the width at the end

public float getFinalHeight()

get the width of the widget at the end of the movement.

Returns:

the height at the end

public java.lang.String getAnimateRelativeTo()

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

Returns:

the view id of the view this is in polar mode to or -1 if not in polar

public void setupRelative(Motion motionController)

set up the motion to be relative to this other motionController

public float getCenterX()

public float getCenterY()

public void getCenter(double p, float[] pos[], float[] vel[])

public void buildPath(float[] points[], int pointCount)

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

Parameters:

points: array to fill (should be 2x the number of mPoints

public int buildKeyFrames(float[] keyFrames[], int[] mode[], int[] pos[])

public void buildRect(float p, float[] path[], int offset)

public void addKey(MotionKey key)

public void setPathMotionArc(int arc)

public void setup(int parentWidth, int parentHeight, float transitionDuration, long currentTime)

Called after all TimePoints & Cycles have been added; Spines are evaluated

public java.lang.String toString()

Debug string

public void setView(MotionWidget view)

public MotionWidget getView()

public void setStart(MotionWidget mw)

public void setEnd(MotionWidget mw)

public void setStartState(ViewState rect, MotionWidget v, int rotation, int preWidth, int preHeight)

public boolean interpolate(MotionWidget child, float globalPosition, long time, KeyCache keyCache)

The main driver of interpolation

Returns:

do you need to keep animating

public void getDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt[])

This returns the differential with respect to the animation layout position (Progress) of a point on the view (post layout effects are not computed)

Parameters:

position: position in time
locationX: the x location on the view (0 = left edge, 1 = right edge)
locationY: the y location on the view (0 = top, 1 = bottom)
mAnchorDpDt: returns the differential of the motion with respect to the position

public int getDrawPath()

public void setDrawPath(int debugMode)

public int getKeyFramePositions(int[] type[], float[] pos[])

Get the keyFrames for the view controlled by this MotionController

Parameters:

type: is position(0-100) + 1000 * mType(1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
pos: the x&y position of the keyFrame along the path

Returns:

Number of keyFrames found

public int getKeyFrameInfo(int type, int[] info[])

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

Parameters:

info: is a data structure array of int that holds info on each keyframe

Returns:

Number of keyFrames found

public boolean setValue(int id, int value)

public boolean setValue(int id, float value)

public boolean setValue(int id, java.lang.String value)

public boolean setValue(int id, boolean value)

public int getId(java.lang.String name)

public void setStaggerScale(float staggerScale)

Set stagger scale

public void setStaggerOffset(float staggerOffset)

set the offset used in calculating stagger launches

Parameters:

staggerOffset: fraction of progress before this controller runs

public float getMotionStagger()

The values set in motion: { stagger: '2' }

Returns:

value from motion: { stagger: ? } or NaN if not set

public void setIdString(java.lang.String stringId)

Source

/*
 * 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;
    }
}