1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.constraintlayout.motion.widget;
18 
19 import static androidx.constraintlayout.widget.ConstraintSet.BASELINE;
20 import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
21 import static androidx.constraintlayout.widget.ConstraintSet.END;
22 import static androidx.constraintlayout.widget.ConstraintSet.HORIZONTAL;
23 import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
24 import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
25 import static androidx.constraintlayout.widget.ConstraintSet.START;
26 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
27 import static androidx.constraintlayout.widget.ConstraintSet.VERTICAL;
28 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
29 
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.View;
33 import android.view.ViewGroup;
34 
35 import androidx.constraintlayout.widget.ConstraintSet;
36 
37 import java.util.HashMap;
38 import java.util.Objects;
39 
40 /**
41  * Utility class to manipulate MotionLayout from the layout editor
42  *
43  *
44  */
45 public class DesignTool {
46 
47     static final HashMap<Pair<Integer, Integer>, String> sAllAttributes = new HashMap<>();
48     static final HashMap<String, String> sAllMargins = new HashMap<>();
49     private static final boolean DEBUG = false;
50     private static final boolean DO_NOT_USE = false;
51     private static final String TAG = "DesignTool";
52 
53     static {
Pair.create(BOTTOM, BOTTOM)54         sAllAttributes.put(Pair.create(BOTTOM, BOTTOM), "layout_constraintBottom_toBottomOf");
Pair.create(BOTTOM, TOP)55         sAllAttributes.put(Pair.create(BOTTOM, TOP), "layout_constraintBottom_toTopOf");
Pair.create(TOP, BOTTOM)56         sAllAttributes.put(Pair.create(TOP, BOTTOM), "layout_constraintTop_toBottomOf");
Pair.create(TOP, TOP)57         sAllAttributes.put(Pair.create(TOP, TOP), "layout_constraintTop_toTopOf");
Pair.create(START, START)58         sAllAttributes.put(Pair.create(START, START), "layout_constraintStart_toStartOf");
Pair.create(START, END)59         sAllAttributes.put(Pair.create(START, END), "layout_constraintStart_toEndOf");
Pair.create(END, START)60         sAllAttributes.put(Pair.create(END, START), "layout_constraintEnd_toStartOf");
Pair.create(END, END)61         sAllAttributes.put(Pair.create(END, END), "layout_constraintEnd_toEndOf");
Pair.create(LEFT, LEFT)62         sAllAttributes.put(Pair.create(LEFT, LEFT), "layout_constraintLeft_toLeftOf");
Pair.create(LEFT, RIGHT)63         sAllAttributes.put(Pair.create(LEFT, RIGHT), "layout_constraintLeft_toRightOf");
Pair.create(RIGHT, RIGHT)64         sAllAttributes.put(Pair.create(RIGHT, RIGHT), "layout_constraintRight_toRightOf");
Pair.create(RIGHT, LEFT)65         sAllAttributes.put(Pair.create(RIGHT, LEFT), "layout_constraintRight_toLeftOf");
Pair.create(BASELINE, BASELINE)66         sAllAttributes.put(Pair.create(BASELINE, BASELINE),
67                 "layout_constraintBaseline_toBaselineOf");
68 
69         sAllMargins.put("layout_constraintBottom_toBottomOf", "layout_marginBottom");
70         sAllMargins.put("layout_constraintBottom_toTopOf", "layout_marginBottom");
71         sAllMargins.put("layout_constraintTop_toBottomOf", "layout_marginTop");
72         sAllMargins.put("layout_constraintTop_toTopOf", "layout_marginTop");
73         sAllMargins.put("layout_constraintStart_toStartOf", "layout_marginStart");
74         sAllMargins.put("layout_constraintStart_toEndOf", "layout_marginStart");
75         sAllMargins.put("layout_constraintEnd_toStartOf", "layout_marginEnd");
76         sAllMargins.put("layout_constraintEnd_toEndOf", "layout_marginEnd");
77         sAllMargins.put("layout_constraintLeft_toLeftOf", "layout_marginLeft");
78         sAllMargins.put("layout_constraintLeft_toRightOf", "layout_marginLeft");
79         sAllMargins.put("layout_constraintRight_toRightOf", "layout_marginRight");
80         sAllMargins.put("layout_constraintRight_toLeftOf", "layout_marginRight");
81     }
82 
83     private final MotionLayout mMotionLayout;
84     private MotionScene mSceneCache;
85     private String mLastStartState = null;
86     private String mLastEndState = null;
87     private int mLastStartStateId = -1;
88     private int mLastEndStateId = -1;
89 
DesignTool(MotionLayout motionLayout)90     public DesignTool(MotionLayout motionLayout) {
91         mMotionLayout = motionLayout;
92     }
93 
getPxFromDp(int dpi, String value)94     private static int getPxFromDp(int dpi, String value) {
95         if (value == null) {
96             return 0;
97         }
98         int index = value.indexOf('d');
99         if (index == -1) {
100             return 0;
101         }
102         String filteredValue = value.substring(0, index);
103         int dpValue = (int) (Integer.valueOf(filteredValue) * dpi / 160f);
104         return dpValue;
105     }
106 
connect(int dpi, ConstraintSet set, View view, HashMap<String, String> attributes, int from, int to)107     private static void connect(int dpi,
108                                 ConstraintSet set,
109                                 View view,
110                                 HashMap<String, String> attributes,
111                                 int from,
112                                 int to) {
113         String connection = sAllAttributes.get(Pair.create(from, to));
114         String connectionValue = attributes.get(connection);
115 
116         if (connectionValue != null) {
117             int marginValue = 0;
118             String margin = sAllMargins.get(connection);
119             if (margin != null) {
120                 marginValue = getPxFromDp(dpi, attributes.get(margin));
121             }
122             int id = Integer.parseInt(connectionValue);
123             set.connect(view.getId(), from, id, to, marginValue);
124         }
125     }
126 
setBias(ConstraintSet set, View view, HashMap<String, String> attributes, int type)127     private static void setBias(ConstraintSet set,
128                                 View view,
129                                 HashMap<String, String> attributes,
130                                 int type) {
131         String bias = "layout_constraintHorizontal_bias";
132         if (type == VERTICAL) {
133             bias = "layout_constraintVertical_bias";
134         }
135         String biasValue = attributes.get(bias);
136         if (biasValue != null) {
137             if (type == HORIZONTAL) {
138                 set.setHorizontalBias(view.getId(), Float.parseFloat(biasValue));
139             } else if (type == VERTICAL) {
140                 set.setVerticalBias(view.getId(), Float.parseFloat(biasValue));
141             }
142         }
143     }
144 
setDimensions(int dpi, ConstraintSet set, View view, HashMap<String, String> attributes, int type)145     private static void setDimensions(int dpi,
146                                       ConstraintSet set,
147                                       View view,
148                                       HashMap<String, String> attributes,
149                                       int type) {
150         String dimension = "layout_width";
151         if (type == VERTICAL) {
152             dimension = "layout_height";
153         }
154         String dimensionValue = attributes.get(dimension);
155         if (dimensionValue != null) {
156             int value = WRAP_CONTENT;
157             if (!dimensionValue.equalsIgnoreCase("wrap_content")) {
158                 value = getPxFromDp(dpi, dimensionValue);
159             }
160             if (type == HORIZONTAL) {
161                 set.constrainWidth(view.getId(), value);
162             } else {
163                 set.constrainHeight(view.getId(), value);
164             }
165         }
166     }
167 
setAbsolutePositions(int dpi, ConstraintSet set, View view, HashMap<String, String> attributes)168     private static void setAbsolutePositions(int dpi,
169                                              ConstraintSet set,
170                                              View view,
171                                              HashMap<String, String> attributes) {
172         String absoluteX = attributes.get("layout_editor_absoluteX");
173         if (absoluteX != null) {
174             set.setEditorAbsoluteX(view.getId(), getPxFromDp(dpi, absoluteX));
175         }
176         String absoluteY = attributes.get("layout_editor_absoluteY");
177         if (absoluteY != null) {
178             set.setEditorAbsoluteY(view.getId(), getPxFromDp(dpi, absoluteY));
179         }
180     }
181 
182     /**
183      * Get the center point of the animation path of a view
184      *
185      * @param view view to getMap the animation of
186      * @param path array to be filled (x1,y1,x2,y2...)
187      * @param len  the desired number of point along animation
188      * @return -1 if not under and animation 0 if not animated or number of point along animation
189      */
getAnimationPath(Object view, float[] path, int len)190     public int getAnimationPath(Object view, float[] path, int len) {
191         if (mMotionLayout.mScene == null) {
192             return -1;
193         }
194 
195         MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
196         if (motionController == null) {
197             return 0;
198         }
199 
200         motionController.buildPath(path, len);
201         return len;
202     }
203 
204     /**
205      * Get the center point of the animation path of a view
206      *
207      * @param view view to getMap the animation of
208      * @param path array to be filled (in groups of 8) (x1,y1,x2,y2...)
209      */
getAnimationRectangles(Object view, float[] path)210     public void getAnimationRectangles(Object view, float[] path) {
211         if (mMotionLayout.mScene == null) {
212             return;
213         }
214         int duration = mMotionLayout.mScene.getDuration();
215         int frames = duration / 16;
216 
217         MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
218         if (motionController == null) {
219             return;
220         }
221 
222         motionController.buildRectangles(path, frames);
223     }
224 
225     /**
226      * Get the location of the start end and key frames
227      *
228      * @param view the view to track
229      * @param key  array to be filled
230      * @return number of key frames + 2
231      */
getAnimationKeyFrames(Object view, float[] key)232     public int getAnimationKeyFrames(Object view, float[] key) {
233         if (mMotionLayout.mScene == null) {
234             return -1;
235         }
236         int duration = mMotionLayout.mScene.getDuration();
237         int frames = duration / 16;
238 
239         MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
240         if (motionController == null) {
241             return 0;
242         }
243 
244         motionController.buildKeyFrames(key, null);
245         return frames;
246     }
247 
248     /**
249      * @param position
250      *
251      */
setToolPosition(float position)252     public void setToolPosition(float position) {
253         if (mMotionLayout.mScene == null) {
254             mMotionLayout.mScene = mSceneCache;
255         }
256         mMotionLayout.setProgress(position);
257         mMotionLayout.evaluate(true);
258         mMotionLayout.requestLayout();
259         mMotionLayout.invalidate();
260     }
261 
262     // @TODO: add description
263 
264     /**
265      *
266      * @return
267      */
getStartState()268     public String getStartState() {
269         int startId = mMotionLayout.getStartState();
270         if (mLastStartStateId == startId) {
271             return mLastStartState;
272         }
273         String last = mMotionLayout.getConstraintSetNames(startId);
274 
275         if (last != null) {
276             mLastStartState = last;
277             mLastStartStateId = startId;
278         }
279         return mMotionLayout.getConstraintSetNames(startId);
280     }
281 
282     // @TODO: add description
283 
284     /**
285      *
286      * @return
287      */
getEndState()288     public String getEndState() {
289         int endId = mMotionLayout.getEndState();
290 
291         if (mLastEndStateId == endId) {
292             return mLastEndState;
293         }
294         String last = mMotionLayout.getConstraintSetNames(endId);
295         if (last != null) {
296             mLastEndState = last;
297             mLastEndStateId = endId;
298         }
299         return last;
300     }
301 
302     /**
303      * Return the current progress of the current transition
304      *
305      * @return current transition's progress
306      */
getProgress()307     public float getProgress() {
308         return mMotionLayout.getProgress();
309     }
310 
311     /**
312      * Return the current state (ConstraintSet id) as a string
313      *
314      * @return the last state set via the design tool bridge
315      */
getState()316     public String getState() {
317         if (mLastStartState != null && mLastEndState != null) {
318             float progress = getProgress();
319             float epsilon = 0.01f;
320             if (progress <= epsilon) {
321                 return mLastStartState;
322             } else if (progress >= 1 - epsilon) {
323                 return mLastEndState;
324             }
325         }
326         return mLastStartState;
327     }
328 
329     /**
330      * This sets the constraint set based on a string. (without the "@+id/")
331      *
332      * @param id
333      */
setState(String id)334     public void setState(String id) {
335         if (id == null) {
336             id = "motion_base";
337         }
338         if (Objects.equals(mLastStartState, id)) {
339             return;
340         }
341 
342         if (DEBUG) {
343             System.out.println("================================");
344             dumpConstraintSet(id);
345         }
346 
347         mLastStartState = id;
348         mLastEndState = null;
349         if (id == null && DO_NOT_USE) { // going to base layout
350             if (mMotionLayout.mScene != null) {
351                 mSceneCache = mMotionLayout.mScene;
352                 mMotionLayout.mScene = null;
353             }
354 
355             mMotionLayout.setProgress(0);
356             mMotionLayout.requestLayout();
357         }
358 
359         if (mMotionLayout.mScene == null) {
360             mMotionLayout.mScene = mSceneCache;
361         }
362 
363         int rscId = mMotionLayout.lookUpConstraintId(id);
364         mLastStartStateId = rscId;
365 
366         if (rscId != 0) {
367             if (rscId == mMotionLayout.getStartState()) {
368                 mMotionLayout.setProgress(0);
369             } else if (rscId == mMotionLayout.getEndState()) {
370                 mMotionLayout.setProgress(1);
371             } else {
372                 mMotionLayout.transitionToState(rscId);
373                 mMotionLayout.setProgress(1);
374             }
375         }
376         mMotionLayout.requestLayout();
377     }
378 
379     /**
380      * Utility method, returns true if we are currently in a transition
381      *
382      * @return true if in a transition, false otherwise
383      */
isInTransition()384     public boolean isInTransition() {
385         return mLastStartState != null && mLastEndState != null;
386     }
387 
388     /**
389      * This sets the constraint set based on a string. (without the "@+id/")
390      *
391      * @param start
392      * @param end
393      */
setTransition(String start, String end)394     public void setTransition(String start, String end) {
395         if (mMotionLayout.mScene == null) {
396             mMotionLayout.mScene = mSceneCache;
397         }
398         int startId = mMotionLayout.lookUpConstraintId(start);
399         int endId = mMotionLayout.lookUpConstraintId(end);
400 
401         mMotionLayout.setTransition(startId, endId);
402         mLastStartStateId = startId;
403         mLastEndStateId = endId;
404 
405         mLastStartState = start;
406         mLastEndState = end;
407     }
408 
409     /**
410      * this allow disabling autoTransitions to prevent design surface
411      * from being in undefined states
412      *
413      * @param disable
414      */
disableAutoTransition(boolean disable)415     public void disableAutoTransition(boolean disable) {
416         mMotionLayout.disableAutoTransition(disable);
417     }
418 
419     /**
420      * Gets the time of the currently set animation.
421      *
422      * @return time in Milliseconds
423      */
getTransitionTimeMs()424     public long getTransitionTimeMs() {
425         return mMotionLayout.getTransitionTimeMs();
426     }
427 
428     /**
429      * Get the keyFrames for the view controlled by this MotionController.
430      * The call is designed to be efficient because it will be called 30x Number of views a second
431      *
432      * @param view the view to return keyframe positions
433      * @param type is pos(0-100) + 1000*mType(1=Attrib, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
434      * @param pos  the x&y position of the keyFrame along the path
435      * @return Number of keyFrames found
436      */
getKeyFramePositions(Object view, int[] type, float[] pos)437     public int getKeyFramePositions(Object view, int[] type, float[] pos) {
438         MotionController controller = mMotionLayout.mFrameArrayList.get((View) view);
439         if (controller == null) {
440             return 0;
441         }
442         return controller.getKeyFramePositions(type, pos);
443     }
444 
445     /**
446      * Get the keyFrames for the view controlled by this MotionController.
447      * The call is designed to be efficient because it will be called 30x Number of views a second
448      *
449      * @param view the view to return keyframe positions
450      * @param type if type is -1, skip all keyframes with type != -1
451      * @param info array to fill with info on each keyframe
452      * @return Number of keyFrames found
453      */
getKeyFrameInfo(Object view, int type, int[] info)454     public int getKeyFrameInfo(Object view, int type, int[] info) {
455         MotionController controller = mMotionLayout.mFrameArrayList.get((View) view);
456         if (controller == null) {
457             return 0;
458         }
459         return controller.getKeyFrameInfo(type, info);
460     }
461 
462     /**
463      * @param view
464      * @param type
465      * @param x
466      * @param y
467      * @return
468      *
469      */
getKeyFramePosition(Object view, int type, float x, float y)470     public float getKeyFramePosition(Object view, int type, float x, float y) {
471         if (!(view instanceof View)) {
472             return 0f;
473         }
474 
475         MotionController mc = mMotionLayout.mFrameArrayList.get((View) view);
476         if (mc == null) {
477             return 0f;
478         }
479 
480         return mc.getKeyFrameParameter(type, x, y);
481     }
482 
483     /**
484      * @param view
485      * @param position
486      * @param name
487      * @param value
488      *
489      */
setKeyFrame(Object view, int position, String name, Object value)490     public void setKeyFrame(Object view, int position, String name, Object value) {
491         if (DEBUG) {
492             Log.v(TAG, "setKeyFrame " + position + " <" + name + "> " + value);
493         }
494         if (mMotionLayout.mScene != null) {
495             mMotionLayout.mScene.setKeyframe((View) view, position, name, value);
496             mMotionLayout.mTransitionGoalPosition = position / 100f;
497             mMotionLayout.mTransitionLastPosition = 0;
498             mMotionLayout.rebuildScene();
499             mMotionLayout.evaluate(true);
500         }
501     }
502 
503     /**
504      * Move the widget directly
505      *
506      * @param view
507      * @param position
508      * @param type
509      * @param x
510      * @param y
511      * @return
512      *
513      */
setKeyFramePosition(Object view, int position, int type, float x, float y)514     public boolean setKeyFramePosition(Object view, int position, int type, float x, float y) {
515         if (!(view instanceof View)) {
516             return false;
517         }
518 
519         if (mMotionLayout.mScene != null) {
520             MotionController controller = mMotionLayout.mFrameArrayList.get(view);
521             position = (int) (mMotionLayout.mTransitionPosition * 100);
522             if (controller != null
523                     && mMotionLayout.mScene.hasKeyFramePosition((View) view, position)) {
524                 float fx = controller.getKeyFrameParameter(MotionController.HORIZONTAL_PATH_X,
525                         x, y);
526                 float fy = controller.getKeyFrameParameter(MotionController.VERTICAL_PATH_Y,
527                         x, y);
528                 // TODO: supports path relative
529                 mMotionLayout.mScene.setKeyframe((View) view, position, "motion:percentX", fx);
530                 mMotionLayout.mScene.setKeyframe((View) view, position, "motion:percentY", fy);
531                 mMotionLayout.rebuildScene();
532                 mMotionLayout.evaluate(true);
533                 mMotionLayout.invalidate();
534                 return true;
535             }
536         }
537         return false;
538     }
539 
540     /**
541      * @param view
542      * @param debugMode
543      *
544      */
setViewDebug(Object view, int debugMode)545     public void setViewDebug(Object view, int debugMode) {
546         if (!(view instanceof View)) {
547             return;
548         }
549 
550         MotionController motionController = mMotionLayout.mFrameArrayList.get(view);
551         if (motionController != null) {
552             motionController.setDrawPath(debugMode);
553             mMotionLayout.invalidate();
554         }
555     }
556 
557     /**
558      * This is a general access to systems in the  MotionLayout System
559      * This provides a series of commands used by the designer to access needed logic
560      * It is written this way to minimize the interface between the library and designer.
561      * It allows the logic to be kept only in the library not replicated in the gui builder.
562      * It also allows us to understand understand the version  of MotionLayout in use
563      * commands
564      * 0 return the version number
565      * 1 Get the center point of the animation path of a view
566      * 2 Get the location of the start end and key frames
567      *
568      * @param cmd        this provide the command needed
569      * @param type       support argument for command
570      * @param viewObject if this command references a view this provides access
571      * @param in         this allows for an array of float to be the input to the system
572      * @param inLength   this provides the length of the input
573      * @param out        this provide the output array
574      * @param outLength  the length of the output array
575      * @return command dependent -1 is typically an error (do not understand)
576      */
designAccess(int cmd, String type, Object viewObject, float[] in, int inLength, float[] out, int outLength)577     public int designAccess(int cmd, String type, Object viewObject,
578                             float[] in, int inLength, float[] out, int outLength) {
579         View view = (View) viewObject;
580         MotionController motionController = null;
581         if (cmd != 0) {
582             if (mMotionLayout.mScene == null) {
583                 return -1;
584             }
585 
586             if (view != null) { // Can't find the view
587                 motionController = mMotionLayout.mFrameArrayList.get(view);
588                 if (motionController == null) {
589                     return -1;
590                 }
591             } else { // currently only cmd  == 0 does not require a motion view
592                 return -1;
593             }
594 
595         }
596         switch (cmd) {
597             case 0: // version
598                 return 1;
599             case 1: { // get View path
600 
601                 int duration = mMotionLayout.mScene.getDuration();
602                 int frames = duration / 16;
603 
604                 motionController.buildPath(out, frames);
605                 return frames;
606             }
607             case 2: { // get key frames
608 
609                 int duration = mMotionLayout.mScene.getDuration();
610                 int frames = duration / 16;
611 
612                 motionController.buildKeyFrames(out, null);
613                 return frames;
614             }
615             case 3: { // get Attribute
616 
617                 int duration = mMotionLayout.mScene.getDuration();
618                 @SuppressWarnings("unused") int frames = duration / 16;
619 
620                 return motionController.getAttributeValues(type, out, outLength);
621             }
622 
623             default:
624                 return -1;
625 
626         }
627     }
628 
629     // @TODO: add description
630 
631     /**
632      *
633      * @param type
634      * @param target
635      * @param position
636      * @return
637      */
getKeyframe(int type, int target, int position)638     public Object getKeyframe(int type, int target, int position) {
639         if (mMotionLayout.mScene == null) {
640             return null;
641         }
642         return mMotionLayout.mScene.getKeyFrame(mMotionLayout.getContext(), type, target, position);
643     }
644 
645     // @TODO: add description
646 
647     /**
648      *
649      * @param viewObject
650      * @param x
651      * @param y
652      * @return
653      */
getKeyframeAtLocation(Object viewObject, float x, float y)654     public Object getKeyframeAtLocation(Object viewObject, float x, float y) {
655         View view = (View) viewObject;
656         MotionController motionController = null;
657         if (mMotionLayout.mScene == null) {
658             return -1;
659         }
660         if (view != null) { // Can't find the view
661             motionController = mMotionLayout.mFrameArrayList.get(view);
662             if (motionController == null) {
663                 return null;
664             }
665         } else {
666             return null;
667         }
668         ViewGroup viewGroup = ((ViewGroup) view.getParent());
669         int layoutWidth = viewGroup.getWidth();
670         int layoutHeight = viewGroup.getHeight();
671         return motionController.getPositionKeyframe(layoutWidth, layoutHeight, x, y);
672     }
673 
674     // @TODO: add description
675 
676     /**
677      *
678      * @param keyFrame
679      * @param view
680      * @param x
681      * @param y
682      * @param attribute
683      * @param value
684      * @return
685      */
getPositionKeyframe(Object keyFrame, Object view, float x, float y, String[] attribute, float[] value)686     public Boolean getPositionKeyframe(Object keyFrame,
687                                        Object view,
688                                        float x,
689                                        float y,
690                                        String[] attribute,
691                                        float[] value) {
692         if (keyFrame instanceof KeyPositionBase) {
693             KeyPositionBase key = (KeyPositionBase) keyFrame;
694             MotionController motionController = mMotionLayout.mFrameArrayList.get((View) view);
695             motionController.positionKeyframe((View) view, key, x, y, attribute, value);
696             mMotionLayout.rebuildScene();
697             mMotionLayout.mInTransition = true;
698             return true;
699         }
700         return false;
701     }
702 
703     // @TODO: add description
704 
705     /**
706      *
707      * @param view
708      * @param type
709      * @param position
710      * @return
711      */
getKeyframe(Object view, int type, int position)712     public Object getKeyframe(Object view, int type, int position) {
713         if (mMotionLayout.mScene == null) {
714             return null;
715         }
716         int target = ((View) view).getId();
717         return mMotionLayout.mScene.getKeyFrame(mMotionLayout.getContext(), type, target, position);
718     }
719 
720     // @TODO: add description
721 
722     /**
723      *
724      * @param keyFrame
725      * @param tag
726      * @param value
727      */
setKeyframe(Object keyFrame, String tag, Object value)728     public void setKeyframe(Object keyFrame, String tag, Object value) {
729         if (keyFrame instanceof Key) {
730             Key key = (Key) keyFrame;
731             key.setValue(tag, value);
732             mMotionLayout.rebuildScene();
733             mMotionLayout.mInTransition = true;
734         }
735     }
736 
737     /**
738      * Live setting of attributes on a view
739      *
740      * @param dpi              dpi used by the application
741      * @param constraintSetId  ConstraintSet id
742      * @param opaqueView       the Android View we operate on, passed as an Object
743      * @param opaqueAttributes the list of attributes (hash<string,string>) we pass to the view
744      */
setAttributes(int dpi, String constraintSetId, Object opaqueView, Object opaqueAttributes)745     public void setAttributes(int dpi,
746                               String constraintSetId,
747                               Object opaqueView,
748                               Object opaqueAttributes) {
749         View view = (View) opaqueView;
750 
751         @SuppressWarnings("unchecked")
752         HashMap<String, String> attributes = (opaqueAttributes instanceof HashMap) ?
753                 (HashMap<String, String>) opaqueAttributes : new HashMap<>();
754 
755         int rscId = mMotionLayout.lookUpConstraintId(constraintSetId);
756         ConstraintSet set = mMotionLayout.mScene.getConstraintSet(rscId);
757 
758         if (DEBUG) {
759             Log.v(TAG, "constraintSetId  = " + constraintSetId + "  " + rscId);
760         }
761 
762         if (set == null) {
763             return;
764         }
765 
766         set.clear(view.getId());
767 
768         setDimensions(dpi, set, view, attributes, HORIZONTAL);
769         setDimensions(dpi, set, view, attributes, VERTICAL);
770 
771         connect(dpi, set, view, attributes, START, START);
772         connect(dpi, set, view, attributes, START, END);
773         connect(dpi, set, view, attributes, END, END);
774         connect(dpi, set, view, attributes, END, START);
775         connect(dpi, set, view, attributes, LEFT, LEFT);
776         connect(dpi, set, view, attributes, LEFT, RIGHT);
777         connect(dpi, set, view, attributes, RIGHT, RIGHT);
778         connect(dpi, set, view, attributes, RIGHT, LEFT);
779         connect(dpi, set, view, attributes, TOP, TOP);
780         connect(dpi, set, view, attributes, TOP, BOTTOM);
781         connect(dpi, set, view, attributes, BOTTOM, TOP);
782         connect(dpi, set, view, attributes, BOTTOM, BOTTOM);
783         connect(dpi, set, view, attributes, BASELINE, BASELINE);
784 
785         setBias(set, view, attributes, HORIZONTAL);
786         setBias(set, view, attributes, VERTICAL);
787 
788         setAbsolutePositions(dpi, set, view, attributes);
789 
790         mMotionLayout.updateState(rscId, set);
791         mMotionLayout.requestLayout();
792     }
793 
794     // @TODO: add description
795 
796     /**
797      *
798      * @param set
799      */
dumpConstraintSet(String set)800     public void dumpConstraintSet(String set) {
801         if (mMotionLayout.mScene == null) {
802             mMotionLayout.mScene = mSceneCache;
803         }
804         int setId = mMotionLayout.lookUpConstraintId(set);
805         System.out.println(" dumping  " + set + " (" + setId + ")");
806         try {
807             mMotionLayout.mScene.getConstraintSet(setId).dump(mMotionLayout.mScene);
808         } catch (Exception ex) {
809             Log.e(TAG, "Error while dumping: " + set + " (" + setId + ")", ex);
810         }
811     }
812 }
813