public class

MotionPaths

extends java.lang.Object

implements java.lang.Comparable<MotionPaths>

 java.lang.Object

↳androidx.constraintlayout.core.motion.MotionPaths

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

This is used to capture and play back path of the layout. It is used to set the bounds of the view (view.layout(l, t, r, b))

Summary

Fields
public static final intCARTESIAN

public static final booleanDEBUG

public java.lang.StringmId

public static final booleanOLD_WAY

public static final intPERPENDICULAR

public static final intSCREEN

public static final java.lang.StringTAG

Constructors
publicMotionPaths()

publicMotionPaths(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)

takes the new keyPosition

Methods
public voidapplyParameters(MotionWidget c)

public intcompareTo(MotionPaths o)

public voidconfigureRelativeTo(Motion toOrbit)

public voidsetupRelative(Motion mc, MotionPaths relative)

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

Fields

public static final java.lang.String TAG

public static final boolean DEBUG

public static final boolean OLD_WAY

public static final int PERPENDICULAR

public static final int CARTESIAN

public static final int SCREEN

public java.lang.String mId

Constructors

public MotionPaths()

public MotionPaths(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)

takes the new keyPosition

Methods

public void setupRelative(Motion mc, MotionPaths relative)

public int compareTo(MotionPaths o)

public void applyParameters(MotionWidget c)

public void configureRelativeTo(Motion toOrbit)

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.MotionKeyPosition;
import androidx.constraintlayout.core.motion.utils.Easing;
import androidx.constraintlayout.core.motion.utils.Utils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;

/**
 * This is used to capture and play back path of the layout.
 * It is used to set the bounds of the view (view.layout(l, t, r, b))
 */
public class MotionPaths implements Comparable<MotionPaths> {
    public static final String TAG = "MotionPaths";
    public static final boolean DEBUG = false;
    public static final boolean OLD_WAY = false; // the computes the positions the old way
    static final int OFF_POSITION = 0;
    static final int OFF_X = 1;
    static final int OFF_Y = 2;
    static final int OFF_WIDTH = 3;
    static final int OFF_HEIGHT = 4;
    static final int OFF_PATH_ROTATE = 5;

    // mode and type have same numbering scheme
    public static final int PERPENDICULAR = MotionKeyPosition.TYPE_PATH;
    public static final int CARTESIAN = MotionKeyPosition.TYPE_CARTESIAN;
    public static final int SCREEN = MotionKeyPosition.TYPE_SCREEN;
    static String[] sNames = {"position", "x", "y", "width", "height", "pathRotate"};
    public String mId;
    Easing mKeyFrameEasing;
    int mDrawPath = 0;
    float mTime;
    float mPosition;
    float mX;
    float mY;
    float mWidth;
    float mHeight;
    float mPathRotate = Float.NaN;
    float mProgress = Float.NaN;
    int mPathMotionArc = UNSET;
    String mAnimateRelativeTo = null;
    float mRelativeAngle = Float.NaN;
    Motion mRelativeToController = null;

    HashMap<String, CustomVariable> mCustomAttributes = new HashMap<>();
    int mMode = 0; // how was this point computed 1=perpendicular 2=deltaRelative
    int mAnimateCircleAngleTo; // since angles loop there are 4 ways we can pic direction

    public MotionPaths() {
    }

    /**
     * set up with Cartesian
     */
    void initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
        float position = c.mFramePosition / 100f;
        MotionPaths point = this;
        point.mTime = position;

        mDrawPath = c.mDrawPath;
        float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
        float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
        float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
        float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;

        point.mPosition = point.mTime;

        float path = position; // the position on the path

        float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
        float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
        float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
        float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
        float pathVectorX = endCenterX - startCenterX;
        float pathVectorY = endCenterY - startCenterY;
        point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
        point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
        point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
        point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);

        float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX;
        float dydx = Float.isNaN(c.mAltPercentY) ? 0 : c.mAltPercentY;
        float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY;
        float dxdy = Float.isNaN(c.mAltPercentX) ? 0 : c.mAltPercentX;
        point.mMode = MotionPaths.CARTESIAN;
        point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx
                + pathVectorY * dxdy - scaleX * scaleWidth / 2);
        point.mY = (int) (startTimePoint.mY + pathVectorX * dydx
                + pathVectorY * dydy - scaleY * scaleHeight / 2);

        point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
        point.mPathMotionArc = c.mPathMotionArc;
    }

    /**
     * takes the new keyPosition
     */
    public MotionPaths(int parentWidth,
            int parentHeight,
            MotionKeyPosition c,
            MotionPaths startTimePoint,
            MotionPaths endTimePoint) {
        if (startTimePoint.mAnimateRelativeTo != null) {
            initPolar(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
            return;
        }
        switch (c.mPositionType) {
            case MotionKeyPosition.TYPE_SCREEN:
                initScreen(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
                return;
            case MotionKeyPosition.TYPE_PATH:
                initPath(c, startTimePoint, endTimePoint);
                return;
            default:
            case MotionKeyPosition.TYPE_CARTESIAN:
                initCartesian(c, startTimePoint, endTimePoint);
                return;
        }
    }

    void initPolar(int parentWidth,
            int parentHeight,
            MotionKeyPosition c,
            MotionPaths s,
            MotionPaths e) {
        float position = c.mFramePosition / 100f;
        this.mTime = position;
        mDrawPath = c.mDrawPath;
        this.mMode = c.mPositionType; // mode and type have same numbering scheme
        float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
        float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
        float scaleX = e.mWidth - s.mWidth;
        float scaleY = e.mHeight - s.mHeight;
        this.mPosition = this.mTime;
        mWidth = (int) (s.mWidth + scaleX * scaleWidth);
        mHeight = (int) (s.mHeight + scaleY * scaleHeight);
        @SuppressWarnings("unused") float startfactor = 1 - position;
        @SuppressWarnings("unused") float endfactor = position;

        switch (c.mPositionType) {
            case MotionKeyPosition.TYPE_SCREEN:
                this.mX = Float.isNaN(c.mPercentX) ? (position * (e.mX - s.mX) + s.mX)
                        : c.mPercentX * Math.min(scaleHeight, scaleWidth);
                this.mY = Float.isNaN(c.mPercentY)
                        ? (position * (e.mY - s.mY) + s.mY) : c.mPercentY;
                break;

            case MotionKeyPosition.TYPE_PATH:
                this.mX = (Float.isNaN(c.mPercentX)
                        ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
                this.mY = (Float.isNaN(c.mPercentY)
                        ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
                break;
            default:
            case MotionKeyPosition.TYPE_CARTESIAN:
                this.mX = (Float.isNaN(c.mPercentX)
                        ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
                this.mY = (Float.isNaN(c.mPercentY)
                        ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
                break;
        }

        this.mAnimateRelativeTo = s.mAnimateRelativeTo;
        this.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
        this.mPathMotionArc = c.mPathMotionArc;
    }

    // @TODO: add description
    public void setupRelative(Motion mc, MotionPaths relative) {
        double dx = mX + mWidth / 2 - relative.mX - relative.mWidth / 2;
        double dy = mY + mHeight / 2 - relative.mY - relative.mHeight / 2;
        mRelativeToController = mc;

        mX = (float) Math.hypot(dy, dx);
        if (Float.isNaN(mRelativeAngle)) {
            mY = (float) (Math.atan2(dy, dx) + Math.PI / 2);
        } else {
            mY = (float) Math.toRadians(mRelativeAngle);
        }
    }

    void initScreen(int parentWidth,
            int parentHeight,
            MotionKeyPosition c,
            MotionPaths startTimePoint,
            MotionPaths endTimePoint) {
        float position = c.mFramePosition / 100f;
        MotionPaths point = this;
        point.mTime = position;

        mDrawPath = c.mDrawPath;
        float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
        float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;

        float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
        float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;

        point.mPosition = point.mTime;

        float path = position; // the position on the path

        float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
        float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
        float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
        float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
        float pathVectorX = endCenterX - startCenterX;
        float pathVectorY = endCenterY - startCenterY;
        point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
        point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
        point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
        point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);

        point.mMode = MotionPaths.SCREEN;
        if (!Float.isNaN(c.mPercentX)) {
            parentWidth -= (int) point.mWidth;
            point.mX = (int) (c.mPercentX * parentWidth);
        }
        if (!Float.isNaN(c.mPercentY)) {
            parentHeight -= (int) point.mHeight;
            point.mY = (int) (c.mPercentY * parentHeight);
        }

        point.mAnimateRelativeTo = mAnimateRelativeTo;
        point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
        point.mPathMotionArc = c.mPathMotionArc;
    }

    void initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {

        float position = c.mFramePosition / 100f;
        MotionPaths point = this;
        point.mTime = position;

        mDrawPath = c.mDrawPath;
        float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
        float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;

        float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
        float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;

        point.mPosition = point.mTime;

        float path = Float.isNaN(c.mPercentX) ? position : c.mPercentX; // the position on the path

        float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
        float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
        float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
        float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
        float pathVectorX = endCenterX - startCenterX;
        float pathVectorY = endCenterY - startCenterY;
        point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
        point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
        point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
        point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
        float perpendicular = Float.isNaN(c.mPercentY)
                ? 0 : c.mPercentY; // the position on the path
        float perpendicularX = -pathVectorY;
        float perpendicularY = pathVectorX;

        float normalX = perpendicularX * perpendicular;
        float normalY = perpendicularY * perpendicular;
        point.mMode = MotionPaths.PERPENDICULAR;
        point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
        point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
        point.mX += normalX;
        point.mY += normalY;

        point.mAnimateRelativeTo = mAnimateRelativeTo;
        point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
        point.mPathMotionArc = c.mPathMotionArc;
    }

    private static float xRotate(float sin, float cos, float cx, float cy, float x, float y) {
        x = x - cx;
        y = y - cy;
        return x * cos - y * sin + cx;
    }

    private static float yRotate(float sin, float cos, float cx, float cy, float x, float y) {
        x = x - cx;
        y = y - cy;
        return x * sin + y * cos + cy;
    }

    private boolean diff(float a, float b) {
        if (Float.isNaN(a) || Float.isNaN(b)) {
            return Float.isNaN(a) != Float.isNaN(b);
        }
        return Math.abs(a - b) > 0.000001f;
    }

    void different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode) {
        int c = 0;
        boolean diffx = diff(mX, points.mX);
        boolean diffy = diff(mY, points.mY);
        mask[c++] |= diff(mPosition, points.mPosition);
        mask[c++] |= diffx || diffy || arcMode;
        mask[c++] |= diffx || diffy || arcMode;
        mask[c++] |= diff(mWidth, points.mWidth);
        mask[c++] |= diff(mHeight, points.mHeight);

    }

    void getCenter(double p, int[] toUse, double[] data, float[] point, int offset) {
        float v_x = mX;
        float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        float translationX = 0, translationY = 0;
        for (int i = 0; i < toUse.length; i++) {
            float value = (float) data[i];

            switch (toUse[i]) {
                case OFF_X:
                    v_x = value;
                    break;
                case OFF_Y:
                    v_y = value;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    break;
            }
        }
        if (mRelativeToController != null) {
            float[] pos = new float[2];
            float[] vel = new float[2];

            mRelativeToController.getCenter(p, pos, vel);
            float rx = pos[0];
            float ry = pos[1];
            float radius = v_x;
            float angle = v_y;
            // TODO Debug angle
            v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
            v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
        }

        point[offset] = v_x + v_width / 2 + translationX;
        point[offset + 1] = v_y + v_height / 2 + translationY;
    }

    void getCenter(double p,
            int[] toUse,
            double[] data,
            float[] point,
            double[] vdata,
            float[] velocity) {
        float v_x = mX;
        float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        float dv_x = 0;
        float dv_y = 0;
        float dv_width = 0;
        float dv_height = 0;

        float translationX = 0, translationY = 0;
        for (int i = 0; i < toUse.length; i++) {
            float value = (float) data[i];
            float dvalue = (float) vdata[i];

            switch (toUse[i]) {
                case OFF_X:
                    v_x = value;
                    dv_x = dvalue;
                    break;
                case OFF_Y:
                    v_y = value;
                    dv_y = dvalue;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    dv_width = dvalue;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    dv_height = dvalue;
                    break;
            }
        }
        float dpos_x = dv_x + dv_width / 2;
        float dpos_y = dv_y + dv_height / 2;

        if (mRelativeToController != null) {
            float[] pos = new float[2];
            float[] vel = new float[2];
            mRelativeToController.getCenter(p, pos, vel);
            float rx = pos[0];
            float ry = pos[1];
            float radius = v_x;
            float angle = v_y;
            float dradius = dv_x;
            float dangle = dv_y;
            float drx = vel[0];
            float dry = vel[1];
            // TODO Debug angle
            v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
            v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
            dpos_x = (float) (drx + dradius * Math.sin(angle) + Math.cos(angle) * dangle);
            dpos_y = (float) (dry - dradius * Math.cos(angle) + Math.sin(angle) * dangle);
        }

        point[0] = v_x + v_width / 2 + translationX;
        point[1] = v_y + v_height / 2 + translationY;
        velocity[0] = dpos_x;
        velocity[1] = dpos_y;
    }

    void getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset) {
        float v_x = mX;
        float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        float translationX = 0, translationY = 0;
        for (int i = 0; i < toUse.length; i++) {
            float value = (float) data[i];

            switch (toUse[i]) {
                case OFF_X:
                    v_x = value;
                    break;
                case OFF_Y:
                    v_y = value;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    break;
            }
        }
        if (mRelativeToController != null) {
            float[] pos = new float[2];
            float[] vel = new float[2];
            mRelativeToController.getCenter(p, pos, vel);
            float rx = pos[0];
            float ry = pos[1];
            float radius = v_x;
            float angle = v_y;
            // TODO Debug angle
            v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
            v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
        }

        point[offset] = v_x + v_width / 2 + translationX;
        point[offset + 1] = v_y + v_height / 2 + translationY;
    }

    void getBounds(int[] toUse, double[] data, float[] point, int offset) {
        @SuppressWarnings("unused") float v_x = mX;
        @SuppressWarnings("unused") float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        for (int i = 0; i < toUse.length; i++) {
            float value = (float) data[i];

            switch (toUse[i]) {
                case OFF_X:
                    v_x = value;
                    break;
                case OFF_Y:
                    v_y = value;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    break;
            }
        }
        point[offset] = v_width;
        point[offset + 1] = v_height;
    }

    double[] mTempValue = new double[18];
    double[] mTempDelta = new double[18];

    // Called on the start Time Point
    void setView(float position,
            MotionWidget view,
            int[] toUse,
            double[] data,
            double[] slope,
            double[] cycle) {
        float v_x = mX;
        float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        float dv_x = 0;
        float dv_y = 0;
        float dv_width = 0;
        float dv_height = 0;
        @SuppressWarnings("unused") float delta_path = 0;
        float path_rotate = Float.NaN;
        @SuppressWarnings("unused") String mod;

        if (toUse.length != 0 && mTempValue.length <= toUse[toUse.length - 1]) {
            int scratch_data_length = toUse[toUse.length - 1] + 1;
            mTempValue = new double[scratch_data_length];
            mTempDelta = new double[scratch_data_length];
        }
        Arrays.fill(mTempValue, Double.NaN);
        for (int i = 0; i < toUse.length; i++) {
            mTempValue[toUse[i]] = data[i];
            mTempDelta[toUse[i]] = slope[i];
        }

        for (int i = 0; i < mTempValue.length; i++) {
            if (Double.isNaN(mTempValue[i]) && (cycle == null || cycle[i] == 0.0)) {
                continue;
            }
            double deltaCycle = (cycle != null) ? cycle[i] : 0.0;
            float value = (float) (Double.isNaN(mTempValue[i])
                    ? deltaCycle : mTempValue[i] + deltaCycle);
            float dvalue = (float) mTempDelta[i];

            switch (i) {
                case OFF_POSITION:
                    delta_path = value;
                    break;
                case OFF_X:
                    v_x = value;
                    dv_x = dvalue;

                    break;
                case OFF_Y:
                    v_y = value;
                    dv_y = dvalue;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    dv_width = dvalue;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    dv_height = dvalue;
                    break;
                case OFF_PATH_ROTATE:
                    path_rotate = value;
                    break;
            }
        }

        if (mRelativeToController != null) {
            float[] pos = new float[2];
            float[] vel = new float[2];

            mRelativeToController.getCenter(position, pos, vel);
            float rx = pos[0];
            float ry = pos[1];
            float radius = v_x;
            float angle = v_y;
            float dradius = dv_x;
            float dangle = dv_y;
            float drx = vel[0];
            float dry = vel[1];

            // TODO Debug angle
            float pos_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
            float pos_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
            float dpos_x = (float) (drx + dradius * Math.sin(angle)
                    + radius * Math.cos(angle) * dangle);
            float dpos_y = (float) (dry - dradius * Math.cos(angle)
                    + radius * Math.sin(angle) * dangle);
            dv_x = dpos_x;
            dv_y = dpos_y;
            v_x = pos_x;
            v_y = pos_y;
            if (slope.length >= 2) {
                slope[0] = dpos_x;
                slope[1] = dpos_y;
            }
            if (!Float.isNaN(path_rotate)) {
                float rot = (float) (path_rotate + Math.toDegrees(Math.atan2(dv_y, dv_x)));
                view.setRotationZ(rot);
            }

        } else {

            if (!Float.isNaN(path_rotate)) {
                float rot = 0;
                float dx = dv_x + dv_width / 2;
                float dy = dv_y + dv_height / 2;
                if (DEBUG) {
                    Utils.log(TAG, "dv_x       =" + dv_x);
                    Utils.log(TAG, "dv_y       =" + dv_y);
                    Utils.log(TAG, "dv_width   =" + dv_width);
                    Utils.log(TAG, "dv_height  =" + dv_height);
                }
                rot += (float) (path_rotate + Math.toDegrees(Math.atan2(dy, dx)));
                view.setRotationZ(rot);
                if (DEBUG) {
                    Utils.log(TAG, "Rotated " + rot + "  = " + dx + "," + dy);
                }
            }
        }

        // Todo: develop a concept of Float layout in MotionWidget widget.layout(float ...)
        int l = (int) (0.5f + v_x);
        int t = (int) (0.5f + v_y);
        int r = (int) (0.5f + v_x + v_width);
        int b = (int) (0.5f + v_y + v_height);
        int i_width = r - l;
        int i_height = b - t;
        if (OLD_WAY) { // This way may produce more stable with and height but risk gaps
            l = (int) v_x;
            t = (int) v_y;
            i_width = (int) v_width;
            i_height = (int) v_height;
            r = l + i_width;
            b = t + i_height;
        }

        // MotionWidget must do Android View measure if layout changes
        view.layout(l, t, r, b);
        if (DEBUG) {
            if (toUse.length > 0) {
                Utils.log(TAG, "setView " + mod);
            }
        }
    }

    void getRect(int[] toUse, double[] data, float[] path, int offset) {
        float v_x = mX;
        float v_y = mY;
        float v_width = mWidth;
        float v_height = mHeight;
        @SuppressWarnings("unused") float delta_path = 0;
        float rotation = 0;
        @SuppressWarnings("unused") float alpha = 0;
        @SuppressWarnings("unused") float rotationX = 0;
        @SuppressWarnings("unused") float rotationY = 0;
        float scaleX = 1;
        float scaleY = 1;
        float pivotX = Float.NaN;
        float pivotY = Float.NaN;
        float translationX = 0;
        float translationY = 0;

        @SuppressWarnings("unused") String mod;

        for (int i = 0; i < toUse.length; i++) {
            float value = (float) data[i];

            switch (toUse[i]) {
                case OFF_POSITION:
                    delta_path = value;
                    break;
                case OFF_X:
                    v_x = value;
                    break;
                case OFF_Y:
                    v_y = value;
                    break;
                case OFF_WIDTH:
                    v_width = value;
                    break;
                case OFF_HEIGHT:
                    v_height = value;
                    break;
            }
        }

        if (mRelativeToController != null) {
            float rx = mRelativeToController.getCenterX();
            float ry = mRelativeToController.getCenterY();
            float radius = v_x;
            float angle = v_y;
            // TODO Debug angle
            v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
            v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
        }

        float x1 = v_x;
        float y1 = v_y;

        float x2 = v_x + v_width;
        float y2 = y1;

        float x3 = x2;
        float y3 = v_y + v_height;

        float x4 = x1;
        float y4 = y3;

        float cx = x1 + v_width / 2;
        float cy = y1 + v_height / 2;

        if (!Float.isNaN(pivotX)) {
            cx = x1 + (x2 - x1) * pivotX;
        }
        if (!Float.isNaN(pivotY)) {

            cy = y1 + (y3 - y1) * pivotY;
        }
        if (scaleX != 1) {
            float midx = (x1 + x2) / 2;
            x1 = (x1 - midx) * scaleX + midx;
            x2 = (x2 - midx) * scaleX + midx;
            x3 = (x3 - midx) * scaleX + midx;
            x4 = (x4 - midx) * scaleX + midx;
        }
        if (scaleY != 1) {
            float midy = (y1 + y3) / 2;
            y1 = (y1 - midy) * scaleY + midy;
            y2 = (y2 - midy) * scaleY + midy;
            y3 = (y3 - midy) * scaleY + midy;
            y4 = (y4 - midy) * scaleY + midy;
        }
        if (rotation != 0) {
            float sin = (float) Math.sin(Math.toRadians(rotation));
            float cos = (float) Math.cos(Math.toRadians(rotation));
            float tx1 = xRotate(sin, cos, cx, cy, x1, y1);
            float ty1 = yRotate(sin, cos, cx, cy, x1, y1);
            float tx2 = xRotate(sin, cos, cx, cy, x2, y2);
            float ty2 = yRotate(sin, cos, cx, cy, x2, y2);
            float tx3 = xRotate(sin, cos, cx, cy, x3, y3);
            float ty3 = yRotate(sin, cos, cx, cy, x3, y3);
            float tx4 = xRotate(sin, cos, cx, cy, x4, y4);
            float ty4 = yRotate(sin, cos, cx, cy, x4, y4);
            x1 = tx1;
            y1 = ty1;
            x2 = tx2;
            y2 = ty2;
            x3 = tx3;
            y3 = ty3;
            x4 = tx4;
            y4 = ty4;
        }

        x1 += translationX;
        y1 += translationY;
        x2 += translationX;
        y2 += translationY;
        x3 += translationX;
        y3 += translationY;
        x4 += translationX;
        y4 += translationY;

        path[offset++] = x1;
        path[offset++] = y1;
        path[offset++] = x2;
        path[offset++] = y2;
        path[offset++] = x3;
        path[offset++] = y3;
        path[offset++] = x4;
        path[offset++] = y4;
    }

    /**
     * mAnchorDpDt
     */
    void setDpDt(float locationX,
            float locationY,
            float[] mAnchorDpDt,
            int[] toUse,
            double[] deltaData,
            double[] data) {

        float d_x = 0;
        float d_y = 0;
        float d_width = 0;
        float d_height = 0;

        float deltaScaleX = 0;
        float deltaScaleY = 0;

        @SuppressWarnings("unused") float mPathRotate = Float.NaN;
        float deltaTranslationX = 0;
        float deltaTranslationY = 0;

        String mod = " dd = ";
        for (int i = 0; i < toUse.length; i++) {
            float deltaV = (float) deltaData[i];
            if (DEBUG) {
                mod += " , D" + sNames[toUse[i]] + "/Dt= " + deltaV;
            }
            switch (toUse[i]) {
                case OFF_POSITION:
                    break;
                case OFF_X:
                    d_x = deltaV;
                    break;
                case OFF_Y:
                    d_y = deltaV;
                    break;
                case OFF_WIDTH:
                    d_width = deltaV;
                    break;
                case OFF_HEIGHT:
                    d_height = deltaV;
                    break;

            }
        }
        if (DEBUG) {
            if (toUse.length > 0) {
                Utils.log(TAG, "setDpDt " + mod);
            }
        }

        float deltaX = d_x - deltaScaleX * d_width / 2;
        float deltaY = d_y - deltaScaleY * d_height / 2;
        float deltaWidth = d_width * (1 + deltaScaleX);
        float deltaHeight = d_height * (1 + deltaScaleY);
        float deltaRight = deltaX + deltaWidth;
        float deltaBottom = deltaY + deltaHeight;
        if (DEBUG) {
            if (toUse.length > 0) {

                Utils.log(TAG, "D x /dt           =" + d_x);
                Utils.log(TAG, "D y /dt           =" + d_y);
                Utils.log(TAG, "D width /dt       =" + d_width);
                Utils.log(TAG, "D height /dt      =" + d_height);
                Utils.log(TAG, "D deltaScaleX /dt =" + deltaScaleX);
                Utils.log(TAG, "D deltaScaleY /dt =" + deltaScaleY);
                Utils.log(TAG, "D deltaX /dt      =" + deltaX);
                Utils.log(TAG, "D deltaY /dt      =" + deltaY);
                Utils.log(TAG, "D deltaWidth /dt  =" + deltaWidth);
                Utils.log(TAG, "D deltaHeight /dt =" + deltaHeight);
                Utils.log(TAG, "D deltaRight /dt  =" + deltaRight);
                Utils.log(TAG, "D deltaBottom /dt =" + deltaBottom);
                Utils.log(TAG, "locationX         =" + locationX);
                Utils.log(TAG, "locationY         =" + locationY);
                Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
                Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
            }
        }

        mAnchorDpDt[0] = deltaX * (1 - locationX) + deltaRight * locationX + deltaTranslationX;
        mAnchorDpDt[1] = deltaY * (1 - locationY) + deltaBottom * locationY + deltaTranslationY;
    }

    void fillStandard(double[] data, int[] toUse) {
        float[] set = {mPosition, mX, mY, mWidth, mHeight, mPathRotate};
        int c = 0;
        for (int i = 0; i < toUse.length; i++) {
            if (toUse[i] < set.length) {
                data[c++] = set[toUse[i]];
            }
        }
    }

    boolean hasCustomData(String name) {
        return mCustomAttributes.containsKey(name);
    }

    int getCustomDataCount(String name) {
        CustomVariable a = mCustomAttributes.get(name);
        if (a == null) {
            return 0;
        }
        return a.numberOfInterpolatedValues();
    }

    int getCustomData(String name, double[] value, int offset) {
        CustomVariable a = mCustomAttributes.get(name);
        if (a == null) {
            return 0;
        } else if (a.numberOfInterpolatedValues() == 1) {
            value[offset] = a.getValueToInterpolate();
            return 1;
        } else {
            int n = a.numberOfInterpolatedValues();
            float[] f = new float[n];
            a.getValuesToInterpolate(f);
            for (int i = 0; i < n; i++) {
                value[offset++] = f[i];
            }
            return n;
        }
    }

    void setBounds(float x, float y, float w, float h) {
        this.mX = x;
        this.mY = y;
        mWidth = w;
        mHeight = h;
    }

    @Override
    public int compareTo(MotionPaths o) {
        return Float.compare(mPosition, o.mPosition);
    }

    // @TODO: add description
    public void applyParameters(MotionWidget c) {
        MotionPaths point = this;
        point.mKeyFrameEasing = Easing.getInterpolator(c.mMotion.mTransitionEasing);
        point.mPathMotionArc = c.mMotion.mPathMotionArc;
        point.mAnimateRelativeTo = c.mMotion.mAnimateRelativeTo;
        point.mPathRotate = c.mMotion.mPathRotate;
        point.mDrawPath = c.mMotion.mDrawPath;
        point.mAnimateCircleAngleTo = c.mMotion.mAnimateCircleAngleTo;
        point.mProgress = c.mPropertySet.mProgress;
        if (c.mWidgetFrame != null && c.mWidgetFrame.widget != null) {
            point.mRelativeAngle = c.mWidgetFrame.widget.mCircleConstraintAngle;
        }

        Set<String> at = c.getCustomAttributeNames();
        for (String s : at) {
            CustomVariable attr = c.getCustomAttribute(s);
            if (attr != null && attr.isContinuous()) {
                this.mCustomAttributes.put(s, attr);
            }
        }
    }

    // @TODO: add description
    public void configureRelativeTo(Motion toOrbit) {
        @SuppressWarnings("unused") double[] p = toOrbit.getPos(mProgress); // get the position
        // in the orbit
    }
}