public class

DesignTool

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.motion.widget.DesignTool

Gradle dependencies

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

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Overview

Utility class to manipulate MotionLayout from the layout editor

Summary

Constructors
publicDesignTool(MotionLayout motionLayout)

Methods
public intdesignAccess(int cmd, java.lang.String type, java.lang.Object viewObject, float[] in[], int inLength, float[] out[], int outLength)

This is a general access to systems in the MotionLayout System This provides a series of commands used by the designer to access needed logic It is written this way to minimize the interface between the library and designer.

public voiddisableAutoTransition(boolean disable)

this allow disabling autoTransitions to prevent design surface from being in undefined states

public voiddumpConstraintSet(java.lang.String set)

public intgetAnimationKeyFrames(java.lang.Object view, float[] key[])

Get the location of the start end and key frames

public intgetAnimationPath(java.lang.Object view, float[] path[], int len)

Get the center point of the animation path of a view

public voidgetAnimationRectangles(java.lang.Object view, float[] path[])

Get the center point of the animation path of a view

public java.lang.StringgetEndState()

public java.lang.ObjectgetKeyframe(int type, int target, int position)

public java.lang.ObjectgetKeyframe(java.lang.Object view, int type, int position)

public java.lang.ObjectgetKeyframeAtLocation(java.lang.Object viewObject, float x, float y)

public intgetKeyFrameInfo(java.lang.Object view, int type, int[] info[])

Get the keyFrames for the view controlled by this MotionController.

public floatgetKeyFramePosition(java.lang.Object view, int type, float x, float y)

public intgetKeyFramePositions(java.lang.Object view, int[] type[], float[] pos[])

Get the keyFrames for the view controlled by this MotionController.

public java.lang.BooleangetPositionKeyframe(java.lang.Object keyFrame, java.lang.Object view, float x, float y, java.lang.String attribute[], float[] value[])

public floatgetProgress()

Return the current progress of the current transition

public java.lang.StringgetStartState()

public java.lang.StringgetState()

Return the current state (ConstraintSet id) as a string

public longgetTransitionTimeMs()

Gets the time of the currently set animation.

public booleanisInTransition()

Utility method, returns true if we are currently in a transition

public voidsetAttributes(int dpi, java.lang.String constraintSetId, java.lang.Object opaqueView, java.lang.Object opaqueAttributes)

Live setting of attributes on a view

public voidsetKeyFrame(java.lang.Object view, int position, java.lang.String name, java.lang.Object value)

public voidsetKeyframe(java.lang.Object keyFrame, java.lang.String tag, java.lang.Object value)

public booleansetKeyFramePosition(java.lang.Object view, int position, int type, float x, float y)

Move the widget directly

public voidsetState(java.lang.String id)

This sets the constraint set based on a string.

public voidsetToolPosition(float position)

public voidsetTransition(java.lang.String start, java.lang.String end)

This sets the constraint set based on a string.

public voidsetViewDebug(java.lang.Object view, int debugMode)

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

Constructors

public DesignTool(MotionLayout motionLayout)

Methods

public int getAnimationPath(java.lang.Object view, float[] path[], int len)

Get the center point of the animation path of a view

Parameters:

view: view to getMap the animation of
path: array to be filled (x1,y1,x2,y2...)

Returns:

-1 if not under and animation 0 if not animated or number of point along animation

public void getAnimationRectangles(java.lang.Object view, float[] path[])

Get the center point of the animation path of a view

Parameters:

view: view to getMap the animation of
path: array to be filled (in groups of 8) (x1,y1,x2,y2...)

public int getAnimationKeyFrames(java.lang.Object view, float[] key[])

Get the location of the start end and key frames

Parameters:

view: the view to track
key: array to be filled

Returns:

number of key frames + 2

public void setToolPosition(float position)

Parameters:

position:

public java.lang.String getStartState()

Returns:

public java.lang.String getEndState()

Returns:

public float getProgress()

Return the current progress of the current transition

Returns:

current transition's progress

public java.lang.String getState()

Return the current state (ConstraintSet id) as a string

Returns:

the last state set via the design tool bridge

public void setState(java.lang.String id)

This sets the constraint set based on a string. (without the "@+id/")

Parameters:

id:

public boolean isInTransition()

Utility method, returns true if we are currently in a transition

Returns:

true if in a transition, false otherwise

public void setTransition(java.lang.String start, java.lang.String end)

This sets the constraint set based on a string. (without the "@+id/")

Parameters:

start:
end:

public void disableAutoTransition(boolean disable)

this allow disabling autoTransitions to prevent design surface from being in undefined states

Parameters:

disable:

public long getTransitionTimeMs()

Gets the time of the currently set animation.

Returns:

time in Milliseconds

public int getKeyFramePositions(java.lang.Object view, int[] type[], float[] pos[])

Get the keyFrames for the view controlled by this MotionController. The call is designed to be efficient because it will be called 30x Number of views a second

Parameters:

view: the view to return keyframe positions
type: is pos(0-100) + 1000*mType(1=Attrib, 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(java.lang.Object view, int type, int[] info[])

Get the keyFrames for the view controlled by this MotionController. The call is designed to be efficient because it will be called 30x Number of views a second

Parameters:

view: the view to return keyframe positions
info:

Returns:

Number of keyFrames found

public float getKeyFramePosition(java.lang.Object view, int type, float x, float y)

Parameters:

view:
type:
x:
y:

Returns:

public void setKeyFrame(java.lang.Object view, int position, java.lang.String name, java.lang.Object value)

Parameters:

view:
position:
name:
value:

public boolean setKeyFramePosition(java.lang.Object view, int position, int type, float x, float y)

Move the widget directly

Parameters:

view:
position:
type:
x:
y:

Returns:

public void setViewDebug(java.lang.Object view, int debugMode)

Parameters:

view:
debugMode:

public int designAccess(int cmd, java.lang.String type, java.lang.Object viewObject, float[] in[], int inLength, float[] out[], int outLength)

This is a general access to systems in the MotionLayout System This provides a series of commands used by the designer to access needed logic It is written this way to minimize the interface between the library and designer. It allows the logic to be kept only in the library not replicated in the gui builder. It also allows us to understand understand the version of MotionLayout in use commands 0 return the version number 1 Get the center point of the animation path of a view 2 Get the location of the start end and key frames

Parameters:

cmd: this provide the command needed
type: support argument for command
viewObject: if this command references a view this provides access
in: this allows for an array of float to be the input to the system
inLength: this provides the length of the input
out: this provide the output array
outLength: the length of the output array

Returns:

command dependent -1 is typically an error (do not understand)

public java.lang.Object getKeyframe(int type, int target, int position)

Parameters:

type:
target:
position:

Returns:

public java.lang.Object getKeyframeAtLocation(java.lang.Object viewObject, float x, float y)

Parameters:

viewObject:
x:
y:

Returns:

public java.lang.Boolean getPositionKeyframe(java.lang.Object keyFrame, java.lang.Object view, float x, float y, java.lang.String attribute[], float[] value[])

Parameters:

keyFrame:
view:
x:
y:
attribute:
value:

Returns:

public java.lang.Object getKeyframe(java.lang.Object view, int type, int position)

Parameters:

view:
type:
position:

Returns:

public void setKeyframe(java.lang.Object keyFrame, java.lang.String tag, java.lang.Object value)

Parameters:

keyFrame:
tag:
value:

public void setAttributes(int dpi, java.lang.String constraintSetId, java.lang.Object opaqueView, java.lang.Object opaqueAttributes)

Live setting of attributes on a view

Parameters:

dpi: dpi used by the application
constraintSetId: ConstraintSet id
opaqueView: the Android View we operate on, passed as an Object
opaqueAttributes: the list of attributes (hash) we pass to the view

public void dumpConstraintSet(java.lang.String set)

Parameters:

set:

Source

/*
 * Copyright (C) 2018 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.motion.widget;

import static androidx.constraintlayout.widget.ConstraintSet.BASELINE;
import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.HORIZONTAL;
import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
import static androidx.constraintlayout.widget.ConstraintSet.START;
import static androidx.constraintlayout.widget.ConstraintSet.TOP;
import static androidx.constraintlayout.widget.ConstraintSet.VERTICAL;
import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;

import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;

import androidx.constraintlayout.widget.ConstraintSet;

import java.util.HashMap;
import java.util.Objects;

/**
 * Utility class to manipulate MotionLayout from the layout editor
 *
 *
 */
public class DesignTool {

    static final HashMap<Pair<Integer, Integer>, String> sAllAttributes = new HashMap<>();
    static final HashMap<String, String> sAllMargins = new HashMap<>();
    private static final boolean DEBUG = false;
    private static final boolean DO_NOT_USE = false;
    private static final String TAG = "DesignTool";

    static {
        sAllAttributes.put(Pair.create(BOTTOM, BOTTOM), "layout_constraintBottom_toBottomOf");
        sAllAttributes.put(Pair.create(BOTTOM, TOP), "layout_constraintBottom_toTopOf");
        sAllAttributes.put(Pair.create(TOP, BOTTOM), "layout_constraintTop_toBottomOf");
        sAllAttributes.put(Pair.create(TOP, TOP), "layout_constraintTop_toTopOf");
        sAllAttributes.put(Pair.create(START, START), "layout_constraintStart_toStartOf");
        sAllAttributes.put(Pair.create(START, END), "layout_constraintStart_toEndOf");
        sAllAttributes.put(Pair.create(END, START), "layout_constraintEnd_toStartOf");
        sAllAttributes.put(Pair.create(END, END), "layout_constraintEnd_toEndOf");
        sAllAttributes.put(Pair.create(LEFT, LEFT), "layout_constraintLeft_toLeftOf");
        sAllAttributes.put(Pair.create(LEFT, RIGHT), "layout_constraintLeft_toRightOf");
        sAllAttributes.put(Pair.create(RIGHT, RIGHT), "layout_constraintRight_toRightOf");
        sAllAttributes.put(Pair.create(RIGHT, LEFT), "layout_constraintRight_toLeftOf");
        sAllAttributes.put(Pair.create(BASELINE, BASELINE),
                "layout_constraintBaseline_toBaselineOf");

        sAllMargins.put("layout_constraintBottom_toBottomOf", "layout_marginBottom");
        sAllMargins.put("layout_constraintBottom_toTopOf", "layout_marginBottom");
        sAllMargins.put("layout_constraintTop_toBottomOf", "layout_marginTop");
        sAllMargins.put("layout_constraintTop_toTopOf", "layout_marginTop");
        sAllMargins.put("layout_constraintStart_toStartOf", "layout_marginStart");
        sAllMargins.put("layout_constraintStart_toEndOf", "layout_marginStart");
        sAllMargins.put("layout_constraintEnd_toStartOf", "layout_marginEnd");
        sAllMargins.put("layout_constraintEnd_toEndOf", "layout_marginEnd");
        sAllMargins.put("layout_constraintLeft_toLeftOf", "layout_marginLeft");
        sAllMargins.put("layout_constraintLeft_toRightOf", "layout_marginLeft");
        sAllMargins.put("layout_constraintRight_toRightOf", "layout_marginRight");
        sAllMargins.put("layout_constraintRight_toLeftOf", "layout_marginRight");
    }

    private final MotionLayout mMotionLayout;
    private MotionScene mSceneCache;
    private String mLastStartState = null;
    private String mLastEndState = null;
    private int mLastStartStateId = -1;
    private int mLastEndStateId = -1;

    public DesignTool(MotionLayout motionLayout) {
        mMotionLayout = motionLayout;
    }

    private static int getPxFromDp(int dpi, String value) {
        if (value == null) {
            return 0;
        }
        int index = value.indexOf('d');
        if (index == -1) {
            return 0;
        }
        String filteredValue = value.substring(0, index);
        int dpValue = (int) (Integer.valueOf(filteredValue) * dpi / 160f);
        return dpValue;
    }

    private static void connect(int dpi,
                                ConstraintSet set,
                                View view,
                                HashMap<String, String> attributes,
                                int from,
                                int to) {
        String connection = sAllAttributes.get(Pair.create(from, to));
        String connectionValue = attributes.get(connection);

        if (connectionValue != null) {
            int marginValue = 0;
            String margin = sAllMargins.get(connection);
            if (margin != null) {
                marginValue = getPxFromDp(dpi, attributes.get(margin));
            }
            int id = Integer.parseInt(connectionValue);
            set.connect(view.getId(), from, id, to, marginValue);
        }
    }

    private static void setBias(ConstraintSet set,
                                View view,
                                HashMap<String, String> attributes,
                                int type) {
        String bias = "layout_constraintHorizontal_bias";
        if (type == VERTICAL) {
            bias = "layout_constraintVertical_bias";
        }
        String biasValue = attributes.get(bias);
        if (biasValue != null) {
            if (type == HORIZONTAL) {
                set.setHorizontalBias(view.getId(), Float.parseFloat(biasValue));
            } else if (type == VERTICAL) {
                set.setVerticalBias(view.getId(), Float.parseFloat(biasValue));
            }
        }
    }

    private static void setDimensions(int dpi,
                                      ConstraintSet set,
                                      View view,
                                      HashMap<String, String> attributes,
                                      int type) {
        String dimension = "layout_width";
        if (type == VERTICAL) {
            dimension = "layout_height";
        }
        String dimensionValue = attributes.get(dimension);
        if (dimensionValue != null) {
            int value = WRAP_CONTENT;
            if (!dimensionValue.equalsIgnoreCase("wrap_content")) {
                value = getPxFromDp(dpi, dimensionValue);
            }
            if (type == HORIZONTAL) {
                set.constrainWidth(view.getId(), value);
            } else {
                set.constrainHeight(view.getId(), value);
            }
        }
    }

    private static void setAbsolutePositions(int dpi,
                                             ConstraintSet set,
                                             View view,
                                             HashMap<String, String> attributes) {
        String absoluteX = attributes.get("layout_editor_absoluteX");
        if (absoluteX != null) {
            set.setEditorAbsoluteX(view.getId(), getPxFromDp(dpi, absoluteX));
        }
        String absoluteY = attributes.get("layout_editor_absoluteY");
        if (absoluteY != null) {
            set.setEditorAbsoluteY(view.getId(), getPxFromDp(dpi, absoluteY));
        }
    }

    /**
     * Get the center point of the animation path of a view
     *
     * @param view view to getMap the animation of
     * @param path array to be filled (x1,y1,x2,y2...)
     * @return -1 if not under and animation 0 if not animated or number of point along animation
     */
    public int getAnimationPath(Object view, float[] path, int len) {
        if (mMotionLayout.mScene == null) {
            return -1;
        }

        MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
        if (motionController == null) {
            return 0;
        }

        motionController.buildPath(path, len);
        return len;
    }

    /**
     * Get the center point of the animation path of a view
     *
     * @param view view to getMap the animation of
     * @param path array to be filled (in groups of 8) (x1,y1,x2,y2...)
     */
    public void getAnimationRectangles(Object view, float[] path) {
        if (mMotionLayout.mScene == null) {
            return;
        }
        int duration = mMotionLayout.mScene.getDuration();
        int frames = duration / 16;

        MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
        if (motionController == null) {
            return;
        }

        motionController.buildRectangles(path, frames);
    }

    /**
     * Get the location of the start end and key frames
     *
     * @param view the view to track
     * @param key  array to be filled
     * @return number of key frames + 2
     */
    public int getAnimationKeyFrames(Object view, float[] key) {
        if (mMotionLayout.mScene == null) {
            return -1;
        }
        int duration = mMotionLayout.mScene.getDuration();
        int frames = duration / 16;

        MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
        if (motionController == null) {
            return 0;
        }

        motionController.buildKeyFrames(key, null);
        return frames;
    }

    /**
     * @param position
     *
     */
    public void setToolPosition(float position) {
        if (mMotionLayout.mScene == null) {
            mMotionLayout.mScene = mSceneCache;
        }
        mMotionLayout.setProgress(position);
        mMotionLayout.evaluate(true);
        mMotionLayout.requestLayout();
        mMotionLayout.invalidate();
    }

    // @TODO: add description

    /**
     *
     * @return
     */
    public String getStartState() {
        int startId = mMotionLayout.getStartState();
        if (mLastStartStateId == startId) {
            return mLastStartState;
        }
        String last = mMotionLayout.getConstraintSetNames(startId);

        if (last != null) {
            mLastStartState = last;
            mLastStartStateId = startId;
        }
        return mMotionLayout.getConstraintSetNames(startId);
    }

    // @TODO: add description

    /**
     *
     * @return
     */
    public String getEndState() {
        int endId = mMotionLayout.getEndState();

        if (mLastEndStateId == endId) {
            return mLastEndState;
        }
        String last = mMotionLayout.getConstraintSetNames(endId);
        if (last != null) {
            mLastEndState = last;
            mLastEndStateId = endId;
        }
        return last;
    }

    /**
     * Return the current progress of the current transition
     *
     * @return current transition's progress
     */
    public float getProgress() {
        return mMotionLayout.getProgress();
    }

    /**
     * Return the current state (ConstraintSet id) as a string
     *
     * @return the last state set via the design tool bridge
     */
    public String getState() {
        if (mLastStartState != null && mLastEndState != null) {
            float progress = getProgress();
            float epsilon = 0.01f;
            if (progress <= epsilon) {
                return mLastStartState;
            } else if (progress >= 1 - epsilon) {
                return mLastEndState;
            }
        }
        return mLastStartState;
    }

    /**
     * This sets the constraint set based on a string. (without the "@+id/")
     *
     * @param id
     */
    public void setState(String id) {
        if (id == null) {
            id = "motion_base";
        }
        if (Objects.equals(mLastStartState, id)) {
            return;
        }

        if (DEBUG) {
            System.out.println("================================");
            dumpConstraintSet(id);
        }

        mLastStartState = id;
        mLastEndState = null;
        if (id == null && DO_NOT_USE) { // going to base layout
            if (mMotionLayout.mScene != null) {
                mSceneCache = mMotionLayout.mScene;
                mMotionLayout.mScene = null;
            }

            mMotionLayout.setProgress(0);
            mMotionLayout.requestLayout();
        }

        if (mMotionLayout.mScene == null) {
            mMotionLayout.mScene = mSceneCache;
        }

        int rscId = mMotionLayout.lookUpConstraintId(id);
        mLastStartStateId = rscId;

        if (rscId != 0) {
            if (rscId == mMotionLayout.getStartState()) {
                mMotionLayout.setProgress(0);
            } else if (rscId == mMotionLayout.getEndState()) {
                mMotionLayout.setProgress(1);
            } else {
                mMotionLayout.transitionToState(rscId);
                mMotionLayout.setProgress(1);
            }
        }
        mMotionLayout.requestLayout();
    }

    /**
     * Utility method, returns true if we are currently in a transition
     *
     * @return true if in a transition, false otherwise
     */
    public boolean isInTransition() {
        return mLastStartState != null && mLastEndState != null;
    }

    /**
     * This sets the constraint set based on a string. (without the "@+id/")
     *
     * @param start
     * @param end
     */
    public void setTransition(String start, String end) {
        if (mMotionLayout.mScene == null) {
            mMotionLayout.mScene = mSceneCache;
        }
        int startId = mMotionLayout.lookUpConstraintId(start);
        int endId = mMotionLayout.lookUpConstraintId(end);

        mMotionLayout.setTransition(startId, endId);
        mLastStartStateId = startId;
        mLastEndStateId = endId;

        mLastStartState = start;
        mLastEndState = end;
    }

    /**
     * this allow disabling autoTransitions to prevent design surface
     * from being in undefined states
     *
     * @param disable
     */
    public void disableAutoTransition(boolean disable) {
        mMotionLayout.disableAutoTransition(disable);
    }

    /**
     * Gets the time of the currently set animation.
     *
     * @return time in Milliseconds
     */
    public long getTransitionTimeMs() {
        return mMotionLayout.getTransitionTimeMs();
    }

    /**
     * Get the keyFrames for the view controlled by this MotionController.
     * The call is designed to be efficient because it will be called 30x Number of views a second
     *
     * @param view the view to return keyframe positions
     * @param type is pos(0-100) + 1000*mType(1=Attrib, 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(Object view, int[] type, float[] pos) {
        MotionController controller = mMotionLayout.mFrameArrayList.get((View) view);
        if (controller == null) {
            return 0;
        }
        return controller.getKeyFramePositions(type, pos);
    }

    /**
     * Get the keyFrames for the view controlled by this MotionController.
     * The call is designed to be efficient because it will be called 30x Number of views a second
     *
     * @param view the view to return keyframe positions
     * @param info
     * @return Number of keyFrames found
     */
    public int getKeyFrameInfo(Object view, int type, int[] info) {
        MotionController controller = mMotionLayout.mFrameArrayList.get((View) view);
        if (controller == null) {
            return 0;
        }
        return controller.getKeyFrameInfo(type, info);
    }

    /**
     * @param view
     * @param type
     * @param x
     * @param y
     * @return
     *
     */
    public float getKeyFramePosition(Object view, int type, float x, float y) {
        if (!(view instanceof View)) {
            return 0f;
        }

        MotionController mc = mMotionLayout.mFrameArrayList.get((View) view);
        if (mc == null) {
            return 0f;
        }

        return mc.getKeyFrameParameter(type, x, y);
    }

    /**
     * @param view
     * @param position
     * @param name
     * @param value
     *
     */
    public void setKeyFrame(Object view, int position, String name, Object value) {
        if (DEBUG) {
            Log.v(TAG, "setKeyFrame " + position + " <" + name + "> " + value);
        }
        if (mMotionLayout.mScene != null) {
            mMotionLayout.mScene.setKeyframe((View) view, position, name, value);
            mMotionLayout.mTransitionGoalPosition = position / 100f;
            mMotionLayout.mTransitionLastPosition = 0;
            mMotionLayout.rebuildScene();
            mMotionLayout.evaluate(true);
        }
    }

    /**
     * Move the widget directly
     *
     * @param view
     * @param position
     * @param type
     * @param x
     * @param y
     * @return
     *
     */
    public boolean setKeyFramePosition(Object view, int position, int type, float x, float y) {
        if (!(view instanceof View)) {
            return false;
        }

        if (mMotionLayout.mScene != null) {
            MotionController controller = mMotionLayout.mFrameArrayList.get(view);
            position = (int) (mMotionLayout.mTransitionPosition * 100);
            if (controller != null
                    && mMotionLayout.mScene.hasKeyFramePosition((View) view, position)) {
                float fx = controller.getKeyFrameParameter(MotionController.HORIZONTAL_PATH_X,
                        x, y);
                float fy = controller.getKeyFrameParameter(MotionController.VERTICAL_PATH_Y,
                        x, y);
                // TODO: supports path relative
                mMotionLayout.mScene.setKeyframe((View) view, position, "motion:percentX", fx);
                mMotionLayout.mScene.setKeyframe((View) view, position, "motion:percentY", fy);
                mMotionLayout.rebuildScene();
                mMotionLayout.evaluate(true);
                mMotionLayout.invalidate();
                return true;
            }
        }
        return false;
    }

    /**
     * @param view
     * @param debugMode
     *
     */
    public void setViewDebug(Object view, int debugMode) {
        if (!(view instanceof View)) {
            return;
        }

        MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
        if (motionController != null) {
            motionController.setDrawPath(debugMode);
            mMotionLayout.invalidate();
        }
    }

    /**
     * This is a general access to systems in the  MotionLayout System
     * This provides a series of commands used by the designer to access needed logic
     * It is written this way to minimize the interface between the library and designer.
     * It allows the logic to be kept only in the library not replicated in the gui builder.
     * It also allows us to understand understand the version  of MotionLayout in use
     * commands
     * 0 return the version number
     * 1 Get the center point of the animation path of a view
     * 2 Get the location of the start end and key frames
     *
     * @param cmd        this provide the command needed
     * @param type       support argument for command
     * @param viewObject if this command references a view this provides access
     * @param in         this allows for an array of float to be the input to the system
     * @param inLength   this provides the length of the input
     * @param out        this provide the output array
     * @param outLength  the length of the output array
     * @return command dependent -1 is typically an error (do not understand)
     */
    public int designAccess(int cmd, String type, Object viewObject,
                            float[] in, int inLength, float[] out, int outLength) {
        View view = (View) viewObject;
        MotionController motionController = null;
        if (cmd != 0) {
            if (mMotionLayout.mScene == null) {
                return -1;
            }

            if (view != null) { // Can't find the view
                motionController = mMotionLayout.mFrameArrayList.get(view);
                if (motionController == null) {
                    return -1;
                }
            } else { // currently only cmd  == 0 does not require a motion view
                return -1;
            }

        }
        switch (cmd) {
            case 0: // version
                return 1;
            case 1: { // get View path

                int duration = mMotionLayout.mScene.getDuration();
                int frames = duration / 16;

                motionController.buildPath(out, frames);
                return frames;
            }
            case 2: { // get key frames

                int duration = mMotionLayout.mScene.getDuration();
                int frames = duration / 16;

                motionController.buildKeyFrames(out, null);
                return frames;
            }
            case 3: { // get Attribute

                int duration = mMotionLayout.mScene.getDuration();
                @SuppressWarnings("unused") int frames = duration / 16;

                return motionController.getAttributeValues(type, out, outLength);
            }

            default:
                return -1;

        }
    }

    // @TODO: add description

    /**
     *
     * @param type
     * @param target
     * @param position
     * @return
     */
    public Object getKeyframe(int type, int target, int position) {
        if (mMotionLayout.mScene == null) {
            return null;
        }
        return mMotionLayout.mScene.getKeyFrame(mMotionLayout.getContext(), type, target, position);
    }

    // @TODO: add description

    /**
     *
     * @param viewObject
     * @param x
     * @param y
     * @return
     */
    public Object getKeyframeAtLocation(Object viewObject, float x, float y) {
        View view = (View) viewObject;
        MotionController motionController = null;
        if (mMotionLayout.mScene == null) {
            return -1;
        }
        if (view != null) { // Can't find the view
            motionController = mMotionLayout.mFrameArrayList.get(view);
            if (motionController == null) {
                return null;
            }
        } else {
            return null;
        }
        ViewGroup viewGroup = ((ViewGroup) view.getParent());
        int layoutWidth = viewGroup.getWidth();
        int layoutHeight = viewGroup.getHeight();
        return motionController.getPositionKeyframe(layoutWidth, layoutHeight, x, y);
    }

    // @TODO: add description

    /**
     *
     * @param keyFrame
     * @param view
     * @param x
     * @param y
     * @param attribute
     * @param value
     * @return
     */
    public Boolean getPositionKeyframe(Object keyFrame,
                                       Object view,
                                       float x,
                                       float y,
                                       String[] attribute,
                                       float[] value) {
        if (keyFrame instanceof KeyPositionBase) {
            KeyPositionBase key = (KeyPositionBase) keyFrame;
            MotionController motionController = mMotionLayout.mFrameArrayList.get((View) view);
            motionController.positionKeyframe((View) view, key, x, y, attribute, value);
            mMotionLayout.rebuildScene();
            mMotionLayout.mInTransition = true;
            return true;
        }
        return false;
    }

    // @TODO: add description

    /**
     *
     * @param view
     * @param type
     * @param position
     * @return
     */
    public Object getKeyframe(Object view, int type, int position) {
        if (mMotionLayout.mScene == null) {
            return null;
        }
        int target = ((View) view).getId();
        return mMotionLayout.mScene.getKeyFrame(mMotionLayout.getContext(), type, target, position);
    }

    // @TODO: add description

    /**
     *
     * @param keyFrame
     * @param tag
     * @param value
     */
    public void setKeyframe(Object keyFrame, String tag, Object value) {
        if (keyFrame instanceof Key) {
            Key key = (Key) keyFrame;
            key.setValue(tag, value);
            mMotionLayout.rebuildScene();
            mMotionLayout.mInTransition = true;
        }
    }

    /**
     * Live setting of attributes on a view
     *
     * @param dpi              dpi used by the application
     * @param constraintSetId  ConstraintSet id
     * @param opaqueView       the Android View we operate on, passed as an Object
     * @param opaqueAttributes the list of attributes (hash<string,string>) we pass to the view
     */
    public void setAttributes(int dpi,
                              String constraintSetId,
                              Object opaqueView,
                              Object opaqueAttributes) {
        View view = (View) opaqueView;

        @SuppressWarnings("unchecked")
        HashMap<String, String> attributes = (opaqueAttributes instanceof HashMap) ?
                (HashMap<String, String>) opaqueAttributes : new HashMap<>();

        int rscId = mMotionLayout.lookUpConstraintId(constraintSetId);
        ConstraintSet set = mMotionLayout.mScene.getConstraintSet(rscId);

        if (DEBUG) {
            Log.v(TAG, "constraintSetId  = " + constraintSetId + "  " + rscId);
        }

        if (set == null) {
            return;
        }

        set.clear(view.getId());

        setDimensions(dpi, set, view, attributes, HORIZONTAL);
        setDimensions(dpi, set, view, attributes, VERTICAL);

        connect(dpi, set, view, attributes, START, START);
        connect(dpi, set, view, attributes, START, END);
        connect(dpi, set, view, attributes, END, END);
        connect(dpi, set, view, attributes, END, START);
        connect(dpi, set, view, attributes, LEFT, LEFT);
        connect(dpi, set, view, attributes, LEFT, RIGHT);
        connect(dpi, set, view, attributes, RIGHT, RIGHT);
        connect(dpi, set, view, attributes, RIGHT, LEFT);
        connect(dpi, set, view, attributes, TOP, TOP);
        connect(dpi, set, view, attributes, TOP, BOTTOM);
        connect(dpi, set, view, attributes, BOTTOM, TOP);
        connect(dpi, set, view, attributes, BOTTOM, BOTTOM);
        connect(dpi, set, view, attributes, BASELINE, BASELINE);

        setBias(set, view, attributes, HORIZONTAL);
        setBias(set, view, attributes, VERTICAL);

        setAbsolutePositions(dpi, set, view, attributes);

        mMotionLayout.updateState(rscId, set);
        mMotionLayout.requestLayout();
    }

    // @TODO: add description

    /**
     *
     * @param set
     */
    public void dumpConstraintSet(String set) {
        if (mMotionLayout.mScene == null) {
            mMotionLayout.mScene = mSceneCache;
        }
        int setId = mMotionLayout.lookUpConstraintId(set);
        System.out.println(" dumping  " + set + " (" + setId + ")");
        try {
            mMotionLayout.mScene.getConstraintSet(setId).dump(mMotionLayout.mScene);
        } catch (Exception ex) {
            Log.e(TAG, "Error while dumping: " + set + " (" + setId + ")", ex);
        }
    }
}