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 android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
20 
21 import static androidx.constraintlayout.motion.widget.MotionScene.Transition.TRANSITION_FLAG_FIRST_DRAW;
22 import static androidx.constraintlayout.motion.widget.MotionScene.Transition.TRANSITION_FLAG_INTERCEPT_TOUCH;
23 import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID;
24 import static androidx.constraintlayout.widget.ConstraintSet.UNSET;
25 
26 import android.annotation.SuppressLint;
27 import android.content.Context;
28 import android.content.res.TypedArray;
29 import android.graphics.Canvas;
30 import android.graphics.DashPathEffect;
31 import android.graphics.Matrix;
32 import android.graphics.Paint;
33 import android.graphics.Path;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.os.Build;
37 import android.os.Bundle;
38 import android.util.AttributeSet;
39 import android.util.Log;
40 import android.util.SparseArray;
41 import android.util.SparseBooleanArray;
42 import android.util.SparseIntArray;
43 import android.view.Display;
44 import android.view.MotionEvent;
45 import android.view.VelocityTracker;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.view.animation.Interpolator;
49 import android.widget.TextView;
50 
51 import androidx.annotation.IdRes;
52 import androidx.constraintlayout.core.motion.utils.KeyCache;
53 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
54 import androidx.constraintlayout.core.widgets.ConstraintWidget;
55 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
56 import androidx.constraintlayout.core.widgets.Flow;
57 import androidx.constraintlayout.core.widgets.Helper;
58 import androidx.constraintlayout.core.widgets.Placeholder;
59 import androidx.constraintlayout.motion.utils.StopLogic;
60 import androidx.constraintlayout.motion.utils.ViewState;
61 import androidx.constraintlayout.widget.Barrier;
62 import androidx.constraintlayout.widget.ConstraintHelper;
63 import androidx.constraintlayout.widget.ConstraintLayout;
64 import androidx.constraintlayout.widget.ConstraintSet;
65 import androidx.constraintlayout.widget.Constraints;
66 import androidx.constraintlayout.widget.R;
67 import androidx.core.view.NestedScrollingParent3;
68 
69 import org.jspecify.annotations.NonNull;
70 import org.jspecify.annotations.Nullable;
71 
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.HashMap;
75 import java.util.List;
76 import java.util.concurrent.CopyOnWriteArrayList;
77 
78 /**
79  * A subclass of ConstraintLayout that supports animating between
80  * various states <b>Added in 2.0</b>
81  * <p>
82  * A {@code MotionLayout} is a subclass of {@link ConstraintLayout}
83  * which supports transitions between between various states ({@link ConstraintSet})
84  * defined in {@link MotionScene}s.
85  * <p>
86  * <b>Note:</b> {@code MotionLayout} is available as a support library that you can use
87  * on Android systems starting with API level 14 (ICS).
88  * </p>
89  * <p>
90  * {@code MotionLayout} links to and requires a {@link MotionScene} file.
91  * The file contains one top level tag "MotionScene"
92  * <h2>LayoutDescription</h2>
93  * <table summary="LayoutDescription">
94  * <tr>
95  * <th>Tags</th><th>Description</th>
96  * </tr>
97  * <tr>
98  * <td>{@code <StateSet> }</td>
99  * <td>Describes states supported by the system (optional)</td>
100  * </tr>
101  * <tr>
102  * <td>{@code <ConstraintSet> }</td>
103  * <td>Describes a constraint set</td>
104  * </tr>
105  * <tr>
106  * <td>{@code <Transition> }</td>
107  * <td>Describes a transition between two states or ConstraintSets</td>
108  * </tr>
109  * <tr>
110  * <td>{@code <ViewTransition> }</td>
111  * <td>Describes a transition of a View within a states or ConstraintSets</td>
112  * </tr>
113  * </table>
114  *
115  * <h2>Transition</h2>
116  * <table summary="Transition attributes & tags">
117  * <tr>
118  * <th>Attributes</th><th>Description</th>
119  * </tr>
120  * <tr>
121  * <td>android:id</td>
122  * <td>The id of the Transition</td>
123  * </tr>
124  * <tr>
125  * <td>constraintSetStart</td>
126  * <td>ConstraintSet to be used as the start constraints or a
127  * layout file to get the constraint from</td>
128  * </tr>
129  * <tr>
130  * <td>constraintSetEnd</td>
131  * <td>ConstraintSet to be used as the end constraints or a
132  * layout file to get the constraint from</td>
133  * </tr>
134  * <tr>
135  * <td>motionInterpolator</td>
136  * <td>The ability to set an overall interpolation (easeInOut, linear, etc.)</td>
137  * </tr>
138  * <tr>
139  * <td>duration</td>
140  * <td>Length of time to take to perform the transition</td>
141  * </tr>
142  * <tr>
143  * <td>staggered</td>
144  * <td>Overrides the Manhattan distance from the top most view in the list of views.
145  * <ul>
146  *     <li>For any view of stagger value {@code S(Vi)}</li>
147  *     <li>With the transition stagger value of {@code TS} (from 0.0 - 1.0)</li>
148  *     <li>The duration of the animation is {@code duration}</li>
149  *     <li>The views animation duration {@code DS = duration * (1 -TS)}</li>
150  *     <li>Call the stagger fraction {@code SFi = (S(Vi) - S(V0)) / (S(Vn) - S(V0))}</li>
151  *     <li>The view starts animating at: {@code (duration-DS) * SFi}</li>
152  * </ul>
153  * </td>
154  * </tr>
155  * <tr>
156  * <td>pathMotionArc</td>
157  * <td>The path will move in arc (quarter ellipses)
158  * key words {startVertical | startHorizontal | flip | none }</td>
159  * </tr>
160  * <tr>
161  * <td>autoTransition</td>
162  * <td>automatically transition from one state to another.
163  * key words {none, jumpToStart, jumpToEnd, animateToStart, animateToEnd}</td>
164  * </tr>
165  * </tr>
166  * <tr>
167  * <td>transitionFlags</td>
168  * <td>flags that adjust the behaviour of Transitions. supports {none, beginOnFirstDraw}
169  *      begin on first draw forces the transition's clock to start when it is first
170  *      displayed not when the begin is called</td>
171  * </tr>
172  * </tr>
173  * <tr>
174  * <td>layoutDuringTransition</td>
175  * <td>Configures MotionLayout on how to react to requestLayout calls during transitions.
176  * Allowed values are {ignoreRequest, honorRequest}</td>
177  * </tr>
178  * <tr>
179  * <td>{@code <OnSwipe> }</td>
180  * <td>Adds support for touch handling (optional)</td>
181  * </tr>
182  * <tr>
183  * <td>{@code <OnClick> }</td>
184  * <td>Adds support for triggering transition (optional)</td>
185  * </tr>
186  * <tr>
187  * <td>{@code <KeyFrameSet> }</td>
188  * <td>Describes a set of Key object which modify the animation between constraint sets.</td>
189  * </tr>
190  * </table>
191  *
192  * <ul>
193  * <li>A transition is typically defined by specifying its start and end ConstraintSets.
194  * You also have the possibility to not specify them, in which case such transition
195  * will become a Default transition.
196  * That Default transition will be applied between any state change that isn't
197  * explicitly covered by a transition.</li>
198  * <li>The starting state of the MotionLayout is defined  to be the constraintSetStart of the first
199  * transition.</li>
200  * <li>If no transition is specified (or only a default Transition)
201  * the MotionLayout tag must contain
202  * a app:currentState to define the starting state of the MotionLayout</li>
203  * </ul>
204  *
205  * <h2>ViewTransition</h2>
206  * <table summary="Transition attributes & tags">
207  * <tr>
208  * <th>Attributes</th><th>Description</th>
209  * </tr>
210  * <tr>
211  * <td>android:id</td>
212  * <td>The id of the ViewTransition</td>
213  * </tr>
214  * <tr>
215  * <td>viewTransitionMode</td>
216  * <td>currentState, allStates, noState transition affect the state of the view
217  * in the current constraintSet or all ConstraintSets or non
218  *      if noState the ViewTransitions are run asynchronous</td>
219  * </tr>
220  * <tr>
221  * <td>onStateTransition</td>
222  * <td>actionDown or actionUp run transition if on touch down or
223  * up if view matches motionTarget</td>
224  * </tr>
225  * <tr>
226  * <td>motionInterpolator</td>
227  * <td>The ability to set an overall interpolation
228  * key words {easeInOut, linear, etc.}</td>
229  * </tr>
230  * <tr>
231  * <td>duration</td>
232  * <td>Length of time to take to perform the {@code ViewTransition}</td>
233  * </tr>
234  * <tr>
235  * <td>pathMotionArc</td>
236  * <td>The path will move in arc (quarter ellipses)
237  * key words {startVertical | startHorizontal | flip | none }</td>
238  * </tr>
239  * <tr>
240  * <td>motionTarget</td>
241  * <td>Apply ViewTransition matching this string or id.</td>
242  * </tr>
243  * </tr>
244  * <tr>
245  * <td>setsTag</td>
246  * <td>set this tag at end of transition</td>
247  * </tr>
248  * </tr>
249  * <tr>
250  * <td>clearsTag</td>
251  * <td>clears this tag at end of transition</td>
252  * </tr>
253  * <tr>
254  * <td>ifTagSet</td>
255  * <td>run transition if this tag is set on view</td>
256  * </tr>
257  * <tr>
258  * <td>ifTagNotSet</td>
259  * <td>run transition if this tag is not set on view/td>
260  * </tr>
261  * <tr>
262  * <td>{@code <OnSwipe> }</td>
263  * <td>Adds support for touch handling (optional)</td>
264  * </tr>
265  * <tr>
266  * <td>{@code <OnClick> }</td>
267  * <td>Adds support for triggering transition (optional)</td>
268  * </tr>
269  * <tr>
270  * <td>{@code <KeyFrameSet> }</td>
271  * <td>Describes a set of Key object which modify the animation between constraint sets.</td>
272  * </tr>
273  * </table>
274  *
275  * <ul>
276  * <li>A Transition is typically defined by specifying its start and end ConstraintSets.
277  * You also have the possibility to not specify them, in which case such transition
278  * will become a Default transition.
279  * That Default transition will be applied between any state change that isn't
280  * explicitly covered by a transition.</li>
281  * <li>The starting state of the MotionLayout is defined to be the constraintSetStart of the first
282  * transition.</li>
283  * <li>If no transition is specified (or only a default Transition) the
284  * MotionLayout tag must contain
285  * a app:currentState to define the starting state of the MotionLayout</li>
286  * </ul>
287  *
288  *
289  * <p>
290  * <h2>OnSwipe (optional)</h2>
291  * <table summary="OnSwipe attributes">
292  * <tr>
293  * <th>Attributes</th><th>Description</th>
294  * </tr>
295  * <tr>
296  * <td>touchAnchorId</td>
297  * <td>Have the drag act as if it is moving the "touchAnchorSide" of this object</td>
298  * </tr>
299  * <tr>
300  * <td>touchRegionId</td>
301  * <td>Limits the region that the touch can be start in to the bounds of this view
302  * (even if the view is invisible)</td>
303  * </tr>
304  * <tr>
305  * <td>touchAnchorSide</td>
306  * <td>The side of the object to move with {top|left|right|bottom}</td>
307  * </tr>
308  * <tr>
309  * <td>maxVelocity</td>
310  * <td>limit the maximum velocity (in progress/sec) of the animation will on touch up.
311  * Default 4</td>
312  * </tr>
313  * <tr>
314  * <td>dragDirection</td>
315  * <td>which side to swipe from {dragUp|dragDown|dragLeft|dragRight}</td>
316  * </tr>
317  * <tr>
318  * <td>maxAcceleration</td>
319  * <td>how quickly the animation will accelerate
320  * (progress/sec/sec) and decelerate on touch up. Default 1.2</td>
321  * </tr>
322  * <tr>
323  * <td>dragScale</td>
324  * <td>scale factor to adjust the swipe by. (e.g. 0.5 would require you to move 2x as much)</td>
325  * </tr>
326  * <td>dragThreshold</td>
327  * <td>How much to drag before swipe gesture runs.
328  * Important for mult-direction swipe. Default is 10. 1 is very sensitive.</td>
329  * </tr>
330  * <tr>
331  * <td>moveWhenScrollAtTop</td>
332  * <td>If the swipe is scrolling and View (such as RecyclerView or NestedScrollView)
333  * do scroll and transition happen at the same time</td>
334  * </tr>
335  * <tr>
336  * <td>onTouchUp</td>
337  * <td>Support for various swipe modes
338  * autoComplete,autoCompleteToStart,autoCompleteToEnd,stop,decelerate,decelerateAndComplete</td>
339  * </tr>
340  * </table>
341  *
342  * <p>
343  * <h2>OnClick (optional)</h2>
344  * <table summary="OnClick attributes">
345  * <tr>
346  * <th>Attributes</th><th>Description</th>
347  * </tr>
348  * <tr>
349  * <td>motionTarget</td>
350  * <td>What view triggers Transition.</td>
351  * </tr>
352  * <tr>
353  * <td>clickAction</td>
354  * <td>Direction for buttons to move the animation.
355  * Or (|) combination of:  toggle, transitionToEnd, transitionToStart, jumpToEnd, jumpToStart</td>
356  * </tr>
357  * </table>
358  *
359  * <p>
360  * <h2>StateSet</h2>
361  * <table summary="StateSet tags & attributes">
362  * <tr>
363  * <td>defaultState</td>
364  * <td>The constraint set or layout to use</td>
365  * </tr>
366  * <tr>
367  * <td>{@code <State> }</td>
368  * <td>The side of the object to move</td>
369  * </tr>
370  * </table>
371  *
372  * <p>
373  * <h2>State</h2>
374  * <table summary="State attributes">
375  * <tr>
376  * <td>android:id</td>
377  * <td>Id of the State</td>
378  * </tr>
379  * <tr>
380  * <td>constraints</td>
381  * <td>Id of the ConstraintSet or the Layout file</td>
382  * </tr>
383  * <tr>
384  * <td>{@code <Variant> }</td>
385  * <td>a different constraintSet/layout to choose if the with or height matches</td>
386  * </tr>
387  * </table>
388  *
389  * <h2>Variant</h2>
390  * <table summary="Variant attributes" >
391  * <tr>
392  * <td>region_widthLessThan</td>
393  * <td>Match if width less than</td>
394  * </tr>
395  * <tr>
396  * <td>region_widthMoreThan</td>
397  * <td>Match if width more than</td>
398  * </tr>
399  * <tr>
400  * <td>region_heightLessThan</td>
401  * <td>Match if height less than</td>
402  * </tr>
403  * <tr>
404  * <td>region_heightMoreThan</td>
405  * <td>Match if height more than</td>
406  * </tr>
407  * <tr>
408  * <td>constraints</td>
409  * <td>Id of the ConstraintSet or layout</td>
410  * </tr>
411  * </table>
412  *
413  * <p>
414  * <h2>ConstraintSet</h2>
415  * <table summary="StateSet tags & attributes">
416  * <tr>
417  * <td>android:id</td>
418  * <td>The id of the ConstraintSet</td>
419  * </tr>
420  * <tr>
421  * <td>deriveConstraintsFrom</td>
422  * <td>The id of another constraintSet which defines the constraints not define in this set.
423  * If not specified the layout defines the undefined constraints.</td>
424  * </tr>
425  * <tr>
426  * <td>{@code <Constraint> }</td>
427  * <td>A ConstraintLayout Constraints + other attributes associated with a view</td>
428  * </tr>
429  * </table>
430  *
431  * <p>
432  * <h2>Constraint</h2>
433  * <p> Constraint supports two forms: <p>1: All of ConstraintLayout + the ones listed below +
434  * {@code <CustomAttribute> }.</p>
435  * <p>Or</p><p>
436  * 2: Combination of tags: {@code <Layout> <PropertySet> <Transform> <Motion> <CustomAttribute> }.
437  * The advantage of using these is that if not present the attributes are taken from the base
438  * layout file. This saves from replicating all the layout tags if only a Motion tag is needed.
439  * If <Layout> is used then all layout attributes in the base are ignored. </p>
440  * </p>
441  * <table summary="Constraint attributes">
442  * <tr>
443  * <td>android:id</td>
444  * <td>Id of the View</td>
445  * </tr>
446  * <tr>
447  * <td>[ConstraintLayout attributes]</td>
448  * <td>Any attribute that is part of ConstraintLayout layout is allowed</td>
449  * </tr>
450  * <tr>
451  * <td>[Standard View attributes]</td>
452  * <td>A collection of view attributes supported by the system (see below)</td>
453  * </tr>
454  * <tr>
455  * <td>transitionEasing</td>
456  * <td>define an easing curve to be used when animating from this point
457  * (e.g. {@code curve(1.0,0,0,1.0)})
458  * or key words {standard | accelerate | decelerate | linear}</td>
459  * </tr>
460  * <tr>
461  * <td>pathMotionArc</td>
462  * <td>the path will move in arc (quarter ellipses)
463  * or key words {startVertical | startHorizontal | none }</td>
464  * </tr>
465  * <tr>
466  * <td>transitionPathRotate</td>
467  * <td>(float) rotate object relative to path taken</td>
468  * </tr>
469  * <tr>
470  * <td>drawPath</td>
471  * <td>draw the path the layout will animate animate</td>
472  * </tr>
473  * <tr>
474  * <td>progress</td>
475  * <td>call method setProgress(float) on this  view
476  * (used to talk to nested ConstraintLayouts etc.)</td>
477  * </tr>
478  * <tr>
479  * <td>{@code <CustomAttribute> }</td>
480  * <td>call a set"name" method via reflection</td>
481  * </tr>
482  * <tr>
483  * <td>{@code <Layout> }</td>
484  * <td>Attributes for the ConstraintLayout e.g. layout_constraintTop_toTopOf</td>
485  * </tr>
486  * <tr>
487  * <td>{@code <PropertySet> }</td>
488  * <td>currently only visibility, alpha, motionProgress,layout_constraintTag.</td>
489  * </tr>
490  * <tr>
491  * <td>{@code <Transform> }</td>
492  * <td>All the view transform API such as android:rotation.</td>
493  * </tr>
494  * <tr>
495  * <td>{@code <Motion> }</td>
496  * <td>Motion Layout control commands such as transitionEasing and pathMotionArc</td>
497  * </tr>
498  * </table>
499  *
500  * <p>
501  * <p>
502  * <h2>Layout</h2>
503  * <table summary="Variant attributes" >
504  * <tr>
505  * <td>[ConstraintLayout attributes]</td>
506  * <td>Also see {@link ConstraintLayout.LayoutParams
507  * ConstraintLayout.LayoutParams} for attributes</td>
508  * </tr>
509  * </table>
510  *
511  * <h2>PropertySet</h2>
512  * <table summary="Variant attributes" >
513  * <tr>
514  * <td>visibility</td>
515  * <td>set the Visibility of the view. One of Visible, invisible or gone</td>
516  * </tr>
517  * <tr>
518  * <td>alpha</td>
519  * <td>setAlpha value</td>
520  * </tr>
521  * <tr>
522  * <td>motionProgress</td>
523  * <td>using reflection call setProgress</td>
524  * </tr>
525  * <tr>
526  * <td>layout_constraintTag</td>
527  * <td>a tagging string to identify the type of object</td>
528  * </tr>
529  * </table>
530  *
531  *
532  * <h2>Transform</h2>
533  * <table summary="Variant attributes" >
534  * <tr>
535  * <td>android:elevation</td>
536  * <td>base z depth of the view.</td>
537  * </tr>
538  * <tr>
539  * <td>android:rotation</td>
540  * <td>rotation of the view, in degrees.</td>
541  * </tr>
542  * <tr>
543  * <td>android:rotationX</td>
544  * <td>rotation of the view around the x axis, in degrees.</td>
545  * </tr>
546  * <tr>
547  * <td>android:rotationY</td>
548  * <td>rotation of the view around the y axis, in degrees.</td>
549  * </tr>
550  * <tr>
551  * <td>android:scaleX</td>
552  * <td>scale of the view in the x direction</td>
553  * </tr>
554  * <tr>
555  * <td>android:scaleY</td>
556  * <td>scale of the view in the y direction.</td>
557  * </tr>
558  * <tr>
559  * <td>android:translationX</td>
560  * <td>translation in x of the view. This value is added post-layout to the  left
561  * property of the view, which is set by its layout.</td>
562  * </tr>
563  * <tr>
564  * <td>android:translationY</td>
565  * <td>translation in y of the view. This value is added post-layout to th e top
566  * property of the view, which is set by its layout</td>
567  * </tr>
568  * <tr>
569  * <td>android:translationZ</td>
570  * <td>translation in z of the view. This value is added to its elevation.</td>
571  * </tr>
572  * </table>
573  *
574  *
575  * <h2>Motion</h2>
576  * <table summary="Variant attributes" >
577  * <tr>
578  * <td>transitionEasing</td>
579  * <td>Defines an acceleration curve.</td>
580  * </tr>
581  * <tr>
582  * <td>pathMotionArc</td>
583  * <td>Says the object should move in a quarter ellipse
584  * unless the motion is vertical or horizontal</td>
585  * </tr>
586  * <tr>
587  * <td>motionPathRotate</td>
588  * <td>set the rotation to the path of the object + this angle.</td>
589  * </tr>
590  * <tr>
591  * <td>drawPath</td>
592  * <td>Debugging utility to draw the motion of the path</td>
593  * </tr>
594  * </table>
595  *
596  *
597  * <h2>CustomAttribute</h2>
598  * <table summary="Variant attributes" >
599  * <tr>
600  * <td>attributeName</td>
601  * <td>The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...)</td>
602  * </tr>
603  * <tr>
604  * <td>customColorValue</td>
605  * <td>The value is a color looking setMyAttr(int )</td>
606  * </tr>
607  * <tr>
608  * <td>customIntegerValue</td>
609  * <td>The value is an integer looking setMyAttr(int )</td>
610  * </tr>
611  * <tr>
612  * <td>customFloatValue</td>
613  * <td>The value is a float looking setMyAttr(float )</td>
614  * </tr>
615  * <tr>
616  * <td>customStringValue</td>
617  * <td>The value is a String looking setMyAttr(String )</td>
618  * </tr>
619  * <tr>
620  * <td>customDimension</td>
621  * <td>The value is a dimension looking setMyAttr(float )</td>
622  * </tr>
623  * <tr>
624  * <td>customBoolean</td>
625  * <td>The value is true or false looking setMyAttr(boolean )</td>
626  * </tr>
627  * </table>
628  *
629  * <p>
630  * <p>
631  * <h2>KeyFrameSet</h2>
632  * <p> This is the container for a collection of Key objects (such as KeyPosition) which provide
633  * information about how the views should move </p>
634  * <table summary="StateSet tags & attributes">
635  * <tr>
636  * <td>{@code <KeyPosition>}</td>
637  * <td>Controls the layout position during animation</td>
638  * </tr>
639  * <tr>
640  * <td>{@code <KeyAttribute>}</td>
641  * <td>Controls the post layout properties during animation</td>
642  * </tr>
643  * <tr>
644  * <td>{@code <KeyCycle>}</td>
645  * <td>Controls oscillations with respect to position
646  * of post layout properties during animation</td>
647  * </tr>
648  * <tr>
649  * <td>{@code <KeyTimeCycle>}</td>
650  * <td>Controls oscillations with respect to time of post layout properties during animation</td>
651  * </tr>
652  * <tr>
653  * <td>{@code <KeyTrigger>}</td>
654  * <td>trigger callbacks into code at fixed point during the animation</td>
655  * </tr>
656  * </table>
657  *
658  * <p>
659  * <h2>KeyPosition</h2>
660  * <table summary="KeyPosition attributes">
661  * <tr>
662  * <td>motionTarget</td>
663  * <td>Id of the View or a regular expression to match layout_ConstraintTag</td>
664  * </tr>
665  * <tr>
666  * <td>framePosition</td>
667  * <td>The point along the interpolation 0 = start 100 = end</td>
668  * </tr
669  * <tr>
670  * <td>transitionEasing</td>
671  * <td>define an easing curve to be used when animating from this point (e.g. curve(1.0,0,0, 1.0))
672  * or key words {standard | accelerate | decelerate | linear }
673  * </td>
674  * </tr>
675  * <tr>
676  * <td>pathMotionArc</td>
677  * <td>The path will move in arc (quarter ellipses)
678  * key words {startVertical | startHorizontal | flip | none }</td>
679  * </tr>
680  * <tr>
681  * <td>keyPositionType</td>
682  * <td>how this keyframe's deviation for linear path is calculated
683  * {deltaRelative | pathRelative|parentRelative}</td>
684  * </tr>
685  * <tr>
686  * <td>percentX</td>
687  * <td>(float) percent distance from start to end along
688  * X axis (deltaRelative) or along the path in pathRelative</td>
689  * </tr>
690  * <tr>
691  * <td>percentY</td>
692  * <td>(float) Percent distance from start to end along Y axis
693  * (deltaRelative) or perpendicular to path in pathRelative</td>
694  * </tr>
695  * <tr>
696  * <td>percentWidth</td>
697  * <td>(float) Percent of change in the width.
698  * Note if the width does not change this has no effect.This overrides sizePercent.</td>
699  * </tr>
700  * <tr>
701  * <td>percentHeight</td>
702  * <td>(float) Percent of change in the width.
703  * Note if the width does not change this has no effect.This overrides sizePercent.</td>
704  * </tr>
705  * <tr>
706  * <td>curveFit</td>
707  * <td>path is traced</td>
708  * </tr>
709  * <tr>
710  * <td>drawPath</td>
711  * <td>Draw the path of the objects layout takes useful for debugging</td>
712  * </tr>
713  * <tr>
714  * <td>sizePercent</td>
715  * <td>If the view changes size this controls how growth of the  size.
716  * (for fixed size objects use KeyAttributes scaleX/X)</td>
717  * </tr>
718  * <tr>
719  * <td>curveFit</td>
720  * <td>selects a path based on straight lines or a path based on a
721  * monotonic spline {linear|spline}</td>
722  * </tr>
723  * </table>
724  *
725  * <p>
726  * <p>
727  * <h2>KeyAttribute</h2>
728  * <table summary="KeyAttribute attributes">
729  * <tr>
730  * <td>motionTarget</td>
731  * <td>Id of the View or a regular expression to match layout_ConstraintTag</td>
732  * </tr>
733  * <tr>
734  * <td>framePosition</td>
735  * <td>The point along the interpolation 0 = start 100 = end</td>
736  * </tr>
737  * <tr>
738  * <td>curveFit</td>
739  * <td>selects a path based on straight lines or a path
740  * based on a monotonic spline {linear|spline}</td>
741  * </tr>
742  * <tr>
743  * <td>transitionEasing</td>
744  * <td>Define an easing curve to be used when animating from this point (e.g. curve(1.0,0,0, 1.0))
745  * or key words {standard | accelerate | decelerate | linear }
746  * </td>
747  * </tr>
748  * <tr>
749  * <td>transitionPathRotate</td>
750  * <td>(float) rotate object relative to path taken</td>
751  * </tr>
752  * <tr>
753  * <td>drawPath</td>
754  * <td>draw the path the layout will animate animate</td>
755  * </tr>
756  * <tr>
757  * <td>motionProgress</td>
758  * <td>call method setProgress(float) on this  view
759  * (used to talk to nested ConstraintLayouts etc.)</td>
760  * </tr>
761  * <tr>
762  * <td>[standard view attributes](except visibility)</td>
763  * <td>A collection of post layout view attributes see below </td>
764  * </tr>
765  * <tr>
766  * <p>
767  * <tr>
768  * <td>{@code <CustomAttribute> }</td>
769  * <td>call a set"name" method via reflection</td>
770  * </tr>
771  * </table>
772  *
773  * <h2>CustomAttribute</h2>
774  * <table summary="Variant attributes" >
775  * <tr>
776  * <td>attributeName</td>
777  * <td>The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...)</td>
778  * </tr>
779  * <tr>
780  * <td>customColorValue</td>
781  * <td>The value is a color looking setMyAttr(int )</td>
782  * </tr>
783  * <tr>
784  * <td>customIntegerValue</td>
785  * <td>The value is an integer looking setMyAttr(int )</td>
786  * </tr>
787  * <tr>
788  * <td>customFloatValue</td>
789  * <td>The value is a float looking setMyAttr(float )</td>
790  * </tr>
791  * <tr>
792  * <td>customStringValue</td>
793  * <td>The value is a String looking setMyAttr(String )</td>
794  * </tr>
795  * <tr>
796  * <td>customDimension</td>
797  * <td>The value is a dimension looking setMyAttr(float )</td>
798  * </tr>
799  * <tr>
800  * <td>customBoolean</td>
801  * <td>The value is true or false looking setMyAttr(boolean )</td>
802  * </tr>
803  * </table>
804  *
805  * </p>
806  * <p>
807  * <h2>KeyCycle</h2>
808  * <table summary="Constraint attributes">
809  * <tr>
810  * <td>motionTarget</td>
811  * <td>Id of the View or a regular expression to match layout_ConstraintTag</td>
812  * </tr>
813  * <tr>
814  * <td>framePosition</td>
815  * <td>The point along the interpolation 0 = start 100 = end</td>
816  * </tr>
817  * <tr>
818  * <td>[Standard View attributes]</td>
819  * <td>A collection of view attributes supported by the system (see below)</td>
820  * </tr>
821  * <tr>
822  * <td>waveShape</td>
823  * <td>The shape of the wave to generate
824  * {sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce}</td>
825  * </tr>
826  * <tr>
827  * <td>wavePeriod</td>
828  * <td>The number of cycles to loop near this region</td>
829  * </tr>
830  * <tr>
831  * <td>waveOffset</td>
832  * <td>offset value added to the attribute</td>
833  * </tr>
834  * <tr>
835  * <td>transitionPathRotate</td>
836  * <td>Cycles applied to rotation relative to the path the view is travelling</td>
837  * </tr>
838  * <tr>
839  * <td>progress</td>
840  * <td>call method setProgress(float) on this  view
841  * (used to talk to nested ConstraintLayouts etc.)</td>
842  * </tr>
843  * <tr>
844  * <td>{@code <CustomAttribute> }</td>
845  * <td>call a set"name" method via reflection (limited to floats)</td>
846  * </tr>
847  * </table>
848  *
849  * <h2>CustomAttribute</h2>
850  * <table summary="Variant attributes" >
851  * <tr>
852  * <td>attributeName</td>
853  * <td>The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...)</td>
854  * </tr>
855  * <tr>
856  * <tr>
857  * <td>customFloatValue</td>
858  * <td>The value is a float looking setMyAttr(float )</td>
859  * </tr>
860  * <tr>
861  * </table>
862  *
863  * <h2>KeyTimeCycle</h2>
864  * <table summary="Constraint attributes">
865  * <tr>
866  * <td>motionTarget</td>
867  * <td>Id of the View or a regular expression to match layout_ConstraintTag</td>
868  * </tr>
869  * <tr>
870  * <td>framePosition</td>
871  * <td>The point along the interpolation 0 = start 100 = end</td>
872  * </tr>
873  * <tr>
874  * <td>[Standard View attributes]</td>
875  * <td>A collection of view attributes supported by the system (see below)</td>
876  * </tr>
877  * <tr>
878  * <td>waveShape</td>
879  * <td>The shape of the wave to generate
880  * {sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce}</td>
881  * </tr>
882  * <tr>
883  * <td>wavePeriod</td>
884  * <td>The number of cycles per second</td>
885  * </tr>
886  * <tr>
887  * <td>waveOffset</td>
888  * <td>offset value added to the attribute</td>
889  * </tr>
890  * <tr>
891  * <td>transitionPathRotate</td>
892  * <td>Cycles applied to rotation relative to the path the view is travelling</td>
893  * </tr>
894  * <tr>
895  * <td>progress</td>
896  * <td>call method setProgress(float) on this  view
897  * (used to talk to nested ConstraintLayouts etc.)</td>
898  * </tr>
899  * <tr>
900  * <td>{@code <CustomAttribute> }</td>
901  * <td>call a set"name" method via reflection (limited to floats)</td>
902  * </tr>
903  * </table>
904  *
905  * <h2>CustomAttribute</h2>
906  * <table summary="Variant attributes" >
907  * <tr>
908  * <td>attributeName</td>
909  * <td>The name of the attribute. Case sensitive. ( MyAttr will look for method setMyAttr(...)</td>
910  * </tr>
911  * <tr>
912  * <tr>
913  * <td>customFloatValue</td>
914  * <td>The value is a float looking setMyAttr(float )</td>
915  * </tr>
916  * <tr>
917  * </table>
918  *
919  * <h2>KeyTrigger</h2>
920  * <table summary="KeyTrigger attributes">
921  * <tr>
922  * <td>motionTarget</td>
923  * <td>Id of the View or a regular expression to match layout_ConstraintTag</td>
924  * </tr>
925  * <tr>
926  * <td>framePosition</td>
927  * <td>The point along the interpolation 0 = start 100 = end</td>
928  * </tr
929  * <tr>
930  * <td>onCross</td>
931  * <td>(method name) on crossing this position call this methods on the t arget
932  * </td>
933  * </tr>
934  * <tr>
935  * <td>onPositiveCross</td>
936  * <td>(method name) on forward crossing of the framePosition call this methods on the target</td>
937  * </tr>
938  * <tr>
939  * <td>onNegativeCross/td>
940  * <td>(method name) backward crossing of the framePosition call this methods on the target</td>
941  * </tr>
942  * <tr>
943  * <td>viewTransitionOnCross</td>
944  * <td>(ViewTransition Id) start a NoState view transition on crossing or hitting target
945  * </td>
946  * </tr>
947  * <tr>
948  * <td>viewTransitionOnPositiveCross</td>
949  * <td>(ViewTransition Id) start a NoState view transition forward crossing of the
950  * framePosition or entering target</td>
951  * </tr>
952  * <tr>
953  * <td>viewTransitionOnNegativeCross/td>
954  * <td>(ViewTransition Id) start a NoState view transition backward crossing of the
955  * framePosition or leaving target</td>
956  * </tr>
957  * <tr>
958  * <td>triggerSlack</td>
959  * <td>(float) do not call trigger again if the framePosition has not moved this
960  * fraction away from the trigger point</td>
961  * </tr>
962  * <tr>
963  * <td>triggerId</td>
964  * <td>(id) call the TransitionListener with this trigger id</td>
965  * </tr>
966  * <tr>
967  * <td>motion_postLayoutCollision</td>
968  * <td>Define motion pre or post layout. Post layout is more expensive but captures
969  * KeyAttributes or KeyCycle motions.</td>
970  * </tr>
971  * <tr>
972  * <td>motion_triggerOnCollision</td>
973  * <td>(id) Trigger if the motionTarget collides with the other motionTarget</td>
974  * </tr>
975  * </table>
976  *
977  * </p>
978  * <p>
979  * <h2>Standard attributes</h2>
980  * <table summary="Constraint attributes">
981  * <tr>
982  * <td>android:visibility</td>
983  * <td>Android view attribute that</td>
984  * </tr>
985  * <tr>
986  * <td>android:alpha</td>
987  * <td>Android view attribute that</td>
988  * </tr>
989  * <tr>
990  * <td>android:elevation</td>
991  * <td>base z depth of the view.</td>
992  * </tr>
993  * <tr>
994  * <td>android:rotation</td>
995  * <td>rotation of the view, in degrees.</td>
996  * </tr>
997  * <tr>
998  * <td>android:rotationX</td>
999  * <td>rotation of the view around the x axis, in degrees.</td>
1000  * </tr>
1001  * <tr>
1002  * <td>android:rotationY</td>
1003  * <td>rotation of the view around the y axis, in degrees.</td>
1004  * </tr>
1005  * <tr>
1006  * <td>android:scaleX</td>
1007  * <td>scale of the view in the x direction.</td>
1008  * </tr>
1009  * <tr>
1010  * <td>android:scaleY</td>
1011  * <td>scale of the view in the y direction.</td>
1012  * </tr>
1013  * <tr>
1014  * <td>android:translationX</td>
1015  * <td>translation in x of the view.</td>
1016  * </tr>
1017  * <tr>
1018  * <td>android:translationY</td>
1019  * <td>translation in y of the view.</td>
1020  * </tr>
1021  * <tr>
1022  * <td>android:translationZ</td>
1023  * <td>translation in z of the view.</td>
1024  * </tr>
1025  * <p>
1026  * </table>
1027  *
1028  * </p>
1029  */
1030 public class MotionLayout extends ConstraintLayout implements
1031         NestedScrollingParent3 {
1032 
1033     public static final int TOUCH_UP_COMPLETE = 0;
1034     public static final int TOUCH_UP_COMPLETE_TO_START = 1;
1035     public static final int TOUCH_UP_COMPLETE_TO_END = 2;
1036     public static final int TOUCH_UP_STOP = 3;
1037     public static final int TOUCH_UP_DECELERATE = 4;
1038     public static final int TOUCH_UP_DECELERATE_AND_COMPLETE = 5;
1039     public static final int TOUCH_UP_NEVER_TO_START = 6;
1040     public static final int TOUCH_UP_NEVER_TO_END = 7;
1041 
1042     static final String TAG = "MotionLayout";
1043     private static final boolean DEBUG = false;
1044 
1045     public static boolean IS_IN_EDIT_MODE;
1046 
1047     MotionScene mScene;
1048     Interpolator mInterpolator;
1049     Interpolator mProgressInterpolator = null;
1050     float mLastVelocity = 0;
1051     private int mBeginState = UNSET;
1052     int mCurrentState = UNSET;
1053     private int mEndState = UNSET;
1054     private int mLastWidthMeasureSpec = 0;
1055     private int mLastHeightMeasureSpec = 0;
1056     private boolean mInteractionEnabled = true;
1057 
1058     HashMap<View, MotionController> mFrameArrayList = new HashMap<>();
1059 
1060     private long mAnimationStartTime = 0;
1061     private float mTransitionDuration = 1f;
1062     float mTransitionPosition = 0.0f;
1063     float mTransitionLastPosition = 0.0f;
1064     private long mTransitionLastTime;
1065     float mTransitionGoalPosition = 0.0f;
1066     private boolean mTransitionInstantly;
1067     boolean mInTransition = false;
1068     boolean mIndirectTransition = false;
1069     private TransitionListener mTransitionListener;
1070     private float mLastPos;
1071     private float mLastY;
1072     public static final int DEBUG_SHOW_NONE = 0;
1073     public static final int DEBUG_SHOW_PROGRESS = 1;
1074     public static final int DEBUG_SHOW_PATH = 2;
1075     int mDebugPath = 0;
1076     // variable used in painting the debug
1077     static final int MAX_KEY_FRAMES = 50;
1078     DevModeDraw mDevModeDraw;
1079     private boolean mTemporalInterpolator = false;
1080     private StopLogic mStopLogic = new StopLogic();
1081     private DecelerateInterpolator mDecelerateLogic = new DecelerateInterpolator();
1082 
1083     private DesignTool mDesignTool;
1084 
1085     boolean mFirstDown = true;
1086 
1087     int mOldWidth;
1088     int mOldHeight;
1089     int mLastLayoutWidth;
1090     int mLastLayoutHeight;
1091 
1092     boolean mUndergoingMotion = false;
1093     float mScrollTargetDX;
1094     float mScrollTargetDY;
1095     long mScrollTargetTime;
1096     float mScrollTargetDT;
1097     private boolean mKeepAnimating = false;
1098 
1099     private ArrayList<MotionHelper> mOnShowHelpers = null;
1100     private ArrayList<MotionHelper> mOnHideHelpers = null;
1101     private ArrayList<MotionHelper> mDecoratorsHelpers = null;
1102     private CopyOnWriteArrayList<TransitionListener> mTransitionListeners = null;
1103     private int mFrames = 0;
1104     private long mLastDrawTime = -1;
1105     private float mLastFps = 0;
1106     private int mListenerState = 0;
1107     private float mListenerPosition = 0.0f;
1108     boolean mIsAnimating = false;
1109 
1110     public static final int VELOCITY_POST_LAYOUT = 0;
1111     public static final int VELOCITY_LAYOUT = 1;
1112     public static final int VELOCITY_STATIC_POST_LAYOUT = 2;
1113     public static final int VELOCITY_STATIC_LAYOUT = 3;
1114 
1115     protected boolean mMeasureDuringTransition = false;
1116     int mStartWrapWidth;
1117     int mStartWrapHeight;
1118     int mEndWrapWidth;
1119     int mEndWrapHeight;
1120     int mWidthMeasureMode;
1121     int mHeightMeasureMode;
1122     float mPostInterpolationPosition;
1123     private KeyCache mKeyCache = new KeyCache();
1124     private boolean mInLayout = false;
1125     private StateCache mStateCache;
1126     private Runnable mOnComplete = null;
1127     private int[] mScheduledTransitionTo = null;
1128     int mScheduledTransitions = 0;
1129     private boolean mInRotation = false;
1130     int mRotatMode = 0;
1131     HashMap<View, ViewState> mPreRotate = new HashMap<>();
1132     private int mPreRotateWidth;
1133     private int mPreRotateHeight;
1134     private int mPreviouseRotation;
1135     Rect mTempRect = new Rect();
1136     private boolean mDelayedApply = false;
1137 
getMotionController(int mTouchAnchorId)1138     MotionController getMotionController(int mTouchAnchorId) {
1139         return mFrameArrayList.get(findViewById(mTouchAnchorId));
1140     }
1141 
1142     enum TransitionState {
1143         UNDEFINED,
1144         SETUP,
1145         MOVING,
1146         FINISHED;
1147     };
1148 
1149     TransitionState mTransitionState = TransitionState.UNDEFINED;
1150     private static final float EPSILON = 0.00001f;
1151 
MotionLayout(@onNull Context context)1152     public MotionLayout(@NonNull Context context) {
1153         super(context);
1154         init(null);
1155     }
1156 
MotionLayout(@onNull Context context, @Nullable AttributeSet attrs)1157     public MotionLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
1158         super(context, attrs);
1159         init(attrs);
1160     }
1161 
MotionLayout(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)1162     public MotionLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
1163         super(context, attrs, defStyleAttr);
1164         init(attrs);
1165     }
1166 
1167     /**
1168      * Subclasses can override to define testClasses
1169      *
1170      * @return
1171      */
getNanoTime()1172     protected long getNanoTime() {
1173         return System.nanoTime();
1174     }
1175 
1176     /**
1177      * Subclasses can override to build test frameworks
1178      *
1179      * @return
1180      */
obtainVelocityTracker()1181     protected MotionTracker obtainVelocityTracker() {
1182         return MyTracker.obtain();
1183     }
1184 
1185     /**
1186      * Disable the transition based on transitionID
1187      * @param transitionID
1188      * @param enable
1189      */
enableTransition(int transitionID, boolean enable)1190     public void enableTransition(int transitionID, boolean enable) {
1191         MotionScene.Transition t = getTransition(transitionID);
1192         if (enable) {
1193             t.setEnabled(true);
1194             return;
1195         } else {
1196             if (t == mScene.mCurrentTransition) { // disabling current transition
1197                 List<MotionScene.Transition> transitions =
1198                         mScene.getTransitionsWithState(mCurrentState);
1199                 for (MotionScene.Transition transition : transitions) {
1200                     if (transition.isEnabled()) {
1201                         mScene.mCurrentTransition = transition;
1202                         break;
1203                     }
1204                 }
1205             }
1206             t.setEnabled(false);
1207         }
1208     }
1209 
1210     /**
1211      * Subclasses can override to build test frameworks
1212      */
1213     protected interface MotionTracker {
recycle()1214         void recycle();
1215 
clear()1216         void clear();
1217 
addMovement(MotionEvent event)1218         void addMovement(MotionEvent event);
1219 
computeCurrentVelocity(int units)1220         void computeCurrentVelocity(int units);
1221 
computeCurrentVelocity(int units, float maxVelocity)1222         void computeCurrentVelocity(int units, float maxVelocity);
1223 
getXVelocity()1224         float getXVelocity();
1225 
getYVelocity()1226         float getYVelocity();
1227 
getXVelocity(int id)1228         float getXVelocity(int id);
1229 
getYVelocity(int id)1230         float getYVelocity(int id);
1231     }
1232 
setState(TransitionState newState)1233     void setState(TransitionState newState) {
1234         if (DEBUG) {
1235             Debug.logStack(TAG, mTransitionState + " -> " + newState + " "
1236                     + Debug.getName(getContext(), mCurrentState), 2);
1237         }
1238         if (newState == TransitionState.FINISHED && mCurrentState == UNSET) {
1239             return;
1240         }
1241         TransitionState oldState = mTransitionState;
1242         mTransitionState = newState;
1243 
1244         if (oldState == TransitionState.MOVING && newState == TransitionState.MOVING) {
1245             fireTransitionChange();
1246         }
1247         switch (oldState) {
1248             case UNDEFINED:
1249             case SETUP:
1250                 if (newState == TransitionState.MOVING) {
1251                     fireTransitionChange();
1252                 }
1253                 if (newState == TransitionState.FINISHED) {
1254                     fireTransitionCompleted();
1255                 }
1256                 break;
1257             case MOVING:
1258                 if (newState == TransitionState.FINISHED) {
1259                     fireTransitionCompleted();
1260                 }
1261                 break;
1262             case FINISHED:
1263                 break;
1264         }
1265     }
1266 
1267     private static class MyTracker implements MotionTracker {
1268         VelocityTracker mTracker;
1269         private static MyTracker sMe = new MyTracker();
1270 
obtain()1271         public static MyTracker obtain() {
1272             sMe.mTracker = VelocityTracker.obtain();
1273             return sMe;
1274         }
1275 
1276         @Override
recycle()1277         public void recycle() {
1278             if (mTracker != null) {
1279                 mTracker.recycle();
1280                 mTracker = null; // not allowed to call after recycle
1281             }
1282         }
1283 
1284         @Override
clear()1285         public void clear() {
1286             if (mTracker != null) {
1287                 mTracker.clear();
1288             }
1289         }
1290 
1291         @Override
addMovement(MotionEvent event)1292         public void addMovement(MotionEvent event) {
1293             if (mTracker != null) {
1294                 mTracker.addMovement(event);
1295             }
1296         }
1297 
1298         @Override
computeCurrentVelocity(int units)1299         public void computeCurrentVelocity(int units) {
1300             if (mTracker != null) {
1301                 mTracker.computeCurrentVelocity(units);
1302             }
1303         }
1304 
1305         @Override
computeCurrentVelocity(int units, float maxVelocity)1306         public void computeCurrentVelocity(int units, float maxVelocity) {
1307             if (mTracker != null) {
1308                 mTracker.computeCurrentVelocity(units, maxVelocity);
1309             }
1310         }
1311 
1312         @Override
getXVelocity()1313         public float getXVelocity() {
1314             if (mTracker != null) {
1315                 return mTracker.getXVelocity();
1316             }
1317             return 0;
1318         }
1319 
1320         @Override
getYVelocity()1321         public float getYVelocity() {
1322             if (mTracker != null) {
1323                 return mTracker.getYVelocity();
1324             }
1325             return 0;
1326         }
1327 
1328         @Override
getXVelocity(int id)1329         public float getXVelocity(int id) {
1330             if (mTracker != null) {
1331                 return mTracker.getXVelocity(id);
1332             }
1333             return 0;
1334         }
1335 
1336         @Override
getYVelocity(int id)1337         public float getYVelocity(int id) {
1338             if (mTracker != null) {
1339                 return getYVelocity(id);
1340             }
1341             return 0;
1342         }
1343     }
1344 
1345     /**
1346      * sets the state to start in. To be used during OnCreate
1347      *
1348      * @param beginId the id of the start constraint set
1349      */
setStartState(int beginId)1350     void setStartState(int beginId) {
1351         if (!isAttachedToWindow()) {
1352             if (mStateCache == null) {
1353                 mStateCache = new StateCache();
1354             }
1355             mStateCache.setStartState(beginId);
1356             mStateCache.setEndState(beginId);
1357             return;
1358         }
1359         mCurrentState = beginId;
1360     }
1361 
1362     /**
1363      * Set a transition explicitly between two constraint sets
1364      *
1365      * @param beginId the id of the start constraint set
1366      * @param endId   the id of the end constraint set
1367      */
setTransition(int beginId, int endId)1368     public void setTransition(int beginId, int endId) {
1369         if (!isAttachedToWindow()) {
1370             if (mStateCache == null) {
1371                 mStateCache = new StateCache();
1372             }
1373             mStateCache.setStartState(beginId);
1374             mStateCache.setEndState(endId);
1375             return;
1376         }
1377 
1378         if (mScene != null) {
1379             mBeginState = beginId;
1380             mEndState = endId;
1381             if (DEBUG) {
1382                 Log.v(TAG, Debug.getLocation() + " setTransition "
1383                         + Debug.getName(getContext(), beginId) + " -> "
1384                         + Debug.getName(getContext(), endId));
1385             }
1386             mScene.setTransition(beginId, endId);
1387             mModel.initFrom(mLayoutWidget, mScene.getConstraintSet(beginId),
1388                     mScene.getConstraintSet(endId));
1389             rebuildScene();
1390             mTransitionLastPosition = 0;
1391             transitionToStart();
1392         }
1393     }
1394 
1395     /**
1396      * Set a transition explicitly to a Transition that has an ID
1397      * The transition must have been named with android:id=...
1398      *
1399      * @param transitionId the id to set
1400      */
setTransition(int transitionId)1401     public void setTransition(int transitionId) {
1402         if (mScene != null) {
1403             MotionScene.Transition transition = getTransition(transitionId);
1404             mBeginState = transition.getStartConstraintSetId();
1405             mEndState = transition.getEndConstraintSetId();
1406 
1407             if (!isAttachedToWindow()) {
1408                 if (mStateCache == null) {
1409                     mStateCache = new StateCache();
1410                 }
1411                 mStateCache.setStartState(mBeginState);
1412                 mStateCache.setEndState(mEndState);
1413                 return;
1414             }
1415 
1416             if (DEBUG) {
1417                 Log.v(TAG, Debug.getLocation() + " setTransition "
1418                         + Debug.getName(getContext(), mBeginState) + " -> "
1419                         + Debug.getName(getContext(), mEndState)
1420                         + "   current=" + Debug.getName(getContext(), mCurrentState));
1421             }
1422 
1423             float pos = Float.NaN;
1424             if (mCurrentState == mBeginState) {
1425                 pos = 0;
1426             } else if (mCurrentState == mEndState) {
1427                 pos = 1;
1428             }
1429             mScene.setTransition(transition);
1430             mModel.initFrom(mLayoutWidget,
1431                     mScene.getConstraintSet(mBeginState),
1432                     mScene.getConstraintSet(mEndState));
1433             rebuildScene();
1434 
1435             if (mTransitionLastPosition != pos) {
1436                 // If the last drawn position isn't the same,
1437                 // we might have to make sure we apply the corresponding constraintset.
1438                 if (pos == 0) {
1439                     endTrigger(true);
1440                     mScene.getConstraintSet(mBeginState).applyTo(this);
1441                 } else if (pos == 1) {
1442                     endTrigger(false);
1443                     mScene.getConstraintSet(mEndState).applyTo(this);
1444                 }
1445             }
1446 
1447             mTransitionLastPosition = Float.isNaN(pos) ? 0 : pos;
1448 
1449             if (Float.isNaN(pos)) {
1450                 Log.v(TAG, Debug.getLocation() + " transitionToStart ");
1451                 transitionToStart();
1452             } else {
1453                 setProgress(pos);
1454             }
1455         }
1456     }
1457 
setTransition(MotionScene.Transition transition)1458     protected void setTransition(MotionScene.Transition transition) {
1459         mScene.setTransition(transition);
1460         setState(TransitionState.SETUP);
1461         if (mCurrentState == mScene.getEndId()) {
1462             mTransitionLastPosition = 1.0f;
1463             mTransitionPosition = 1.0f;
1464             mTransitionGoalPosition = 1;
1465         } else {
1466             mTransitionLastPosition = 0;
1467             mTransitionPosition = 0f;
1468             mTransitionGoalPosition = 0;
1469         }
1470         mTransitionLastTime =
1471                 transition.isTransitionFlag(TRANSITION_FLAG_FIRST_DRAW) ? -1 : getNanoTime();
1472         if (DEBUG) {
1473             Log.v(TAG, Debug.getLocation() + "  new mTransitionLastPosition = "
1474                     + mTransitionLastPosition + "");
1475             Log.v(TAG, Debug.getLocation() + " setTransition was "
1476                     + Debug.getName(getContext(), mBeginState)
1477                     + " -> " + Debug.getName(getContext(), mEndState));
1478         }
1479         int newBeginState = mScene.getStartId();
1480         int newEndState = mScene.getEndId();
1481         if (newBeginState == mBeginState && newEndState == mEndState) {
1482             return;
1483         }
1484         mBeginState = newBeginState;
1485         mEndState = newEndState;
1486         mScene.setTransition(mBeginState, mEndState);
1487 
1488         if (DEBUG) {
1489             Log.v(TAG, Debug.getLocation() + " setTransition now "
1490                     + Debug.getName(getContext(), mBeginState) + " -> "
1491                     + Debug.getName(getContext(), mEndState));
1492         }
1493 
1494         mModel.initFrom(mLayoutWidget,
1495                 mScene.getConstraintSet(mBeginState),
1496                 mScene.getConstraintSet(mEndState));
1497         mModel.setMeasuredId(mBeginState, mEndState);
1498         mModel.reEvaluateState();
1499 
1500         rebuildScene();
1501     }
1502 
1503     /**
1504      * This overrides ConstraintLayout and only accepts a MotionScene.
1505      *
1506      * @param motionScene The resource id, or 0 to reset the MotionScene.
1507      */
1508     @Override
loadLayoutDescription(int motionScene)1509     public void loadLayoutDescription(int motionScene) {
1510         if (motionScene != 0) {
1511             try {
1512                 mScene = new MotionScene(getContext(), this, motionScene);
1513                 if (mCurrentState == UNSET && mScene != null) {
1514                     mCurrentState = mScene.getStartId();
1515                     mBeginState = mScene.getStartId();
1516                     mEndState = mScene.getEndId();
1517                 }
1518                 if (isAttachedToWindow()) {
1519                     try {
1520                         Display display = getDisplay();
1521                         mPreviouseRotation = (display == null) ? 0 : display.getRotation();
1522 
1523                         if (mScene != null) {
1524                             ConstraintSet cSet = mScene.getConstraintSet(mCurrentState);
1525                             mScene.readFallback(this);
1526                             if (mDecoratorsHelpers != null) {
1527                                 for (MotionHelper mh : mDecoratorsHelpers) {
1528                                     mh.onFinishedMotionScene(this);
1529                                 }
1530                             }
1531                             if (cSet != null) {
1532                                 cSet.applyTo(this);
1533                             }
1534                             mBeginState = mCurrentState;
1535                         }
1536                         onNewStateAttachHandlers();
1537                         if (mStateCache != null) {
1538                             if (mDelayedApply) {
1539                                 post(new Runnable() {
1540                                     @Override
1541                                     public void run() {
1542                                         mStateCache.apply();
1543                                     }
1544                                 });
1545                             } else {
1546                                 mStateCache.apply();
1547                             }
1548                         } else {
1549                             if (mScene != null && mScene.mCurrentTransition != null) {
1550                                 if (mScene.mCurrentTransition.getAutoTransition()
1551                                         == MotionScene.Transition.AUTO_ANIMATE_TO_END) {
1552                                     transitionToEnd();
1553                                     setState(TransitionState.SETUP);
1554                                     setState(TransitionState.MOVING);
1555                                 }
1556                             }
1557 
1558                         }
1559                     } catch (Exception ex) {
1560                         throw new IllegalArgumentException("unable to parse MotionScene file", ex);
1561                     }
1562                 } else {
1563                     mScene = null;
1564                 }
1565 
1566             } catch (Exception ex) {
1567                 throw new IllegalArgumentException("unable to parse MotionScene file", ex);
1568             }
1569         } else {
1570             mScene = null;
1571         }
1572     }
1573 
1574     /**
1575      * Set the State of the Constraint layout. Causing it to load a particular ConstraintSet.
1576      * for states with variants the variant with matching
1577      * width and height constraintSet will be chosen
1578      *
1579      * @param id           set the state width and height
1580      * @param screenWidth
1581      * @param screenHeight
1582      */
1583     @Override
setState(int id, int screenWidth, int screenHeight)1584     public void setState(int id, int screenWidth, int screenHeight) {
1585         setState(TransitionState.SETUP);
1586         mCurrentState = id;
1587         mBeginState = UNSET;
1588         mEndState = UNSET;
1589         if (mConstraintLayoutSpec != null) {
1590             mConstraintLayoutSpec.updateConstraints(id, screenWidth, screenHeight);
1591         } else if (mScene != null) {
1592             mScene.getConstraintSet(id).applyTo(this);
1593         }
1594     }
1595 
1596     /**
1597      * Set the transition position between 0 an 1
1598      *
1599      * @param pos
1600      */
setInterpolatedProgress(float pos)1601     public void setInterpolatedProgress(float pos) {
1602         if (mScene != null) {
1603             setState(TransitionState.MOVING);
1604             Interpolator interpolator = mScene.getInterpolator();
1605             if (interpolator != null) {
1606                 setProgress(interpolator.getInterpolation(pos));
1607                 return;
1608             }
1609         }
1610         setProgress(pos);
1611     }
1612 
1613     /**
1614      * Set the transition position between 0 an 1
1615      *
1616      * @param pos
1617      * @param velocity
1618      */
setProgress(float pos, float velocity)1619     public void setProgress(float pos, float velocity) {
1620         if (!isAttachedToWindow()) {
1621             if (mStateCache == null) {
1622                 mStateCache = new StateCache();
1623             }
1624             mStateCache.setProgress(pos);
1625             mStateCache.setVelocity(velocity);
1626             return;
1627         }
1628         setProgress(pos);
1629         setState(TransitionState.MOVING);
1630         mLastVelocity = velocity;
1631         if (velocity != 0.0f) {
1632             animateTo(velocity > 0 ? 1 : 0);
1633         } else if (pos != 0f && pos != 1f) {
1634             animateTo(pos > 0.5f ? 1 : 0);
1635         }
1636     }
1637 
1638     /////////////////////// use to cache the state
1639     class StateCache {
1640         float mProgress = Float.NaN;
1641         float mVelocity = Float.NaN;
1642         int mStartState = UNSET;
1643         int mEndState = UNSET;
1644         final String mKeyProgress = "motion.progress";
1645         final String mKeyVelocity = "motion.velocity";
1646         final String mKeyStartState = "motion.StartState";
1647         final String mKeyEndState = "motion.EndState";
1648 
apply()1649         void apply() {
1650             if (this.mStartState != UNSET || this.mEndState != UNSET) {
1651                 if (this.mStartState == UNSET) {
1652                     transitionToState(mEndState);
1653                 } else if (this.mEndState == UNSET) {
1654                     setState(this.mStartState, -1, -1);
1655                 } else {
1656                     setTransition(mStartState, mEndState);
1657                 }
1658                 setState(TransitionState.SETUP);
1659             }
1660             if (Float.isNaN(this.mVelocity)) {
1661                 if (Float.isNaN(this.mProgress)) {
1662                     return;
1663                 }
1664                 MotionLayout.this.setProgress(this.mProgress);
1665                 return;
1666             }
1667             MotionLayout.this.setProgress(this.mProgress, mVelocity);
1668             this.mProgress = Float.NaN;
1669             this.mVelocity = Float.NaN;
1670             this.mStartState = UNSET;
1671             this.mEndState = UNSET;
1672         }
1673 
getTransitionState()1674         public Bundle getTransitionState() {
1675             Bundle bundle = new Bundle();
1676             bundle.putFloat(mKeyProgress, this.mProgress);
1677             bundle.putFloat(mKeyVelocity, this.mVelocity);
1678             bundle.putInt(mKeyStartState, this.mStartState);
1679             bundle.putInt(mKeyEndState, this.mEndState);
1680             return bundle;
1681         }
1682 
setTransitionState(Bundle bundle)1683         public void setTransitionState(Bundle bundle) {
1684             this.mProgress = bundle.getFloat(mKeyProgress);
1685             this.mVelocity = bundle.getFloat(mKeyVelocity);
1686             this.mStartState = bundle.getInt(mKeyStartState);
1687             this.mEndState = bundle.getInt(mKeyEndState);
1688         }
1689 
setProgress(float progress)1690         public void setProgress(float progress) {
1691             this.mProgress = progress;
1692         }
1693 
setEndState(int endState)1694         public void setEndState(int endState) {
1695             this.mEndState = endState;
1696         }
1697 
setVelocity(float mVelocity)1698         public void setVelocity(float mVelocity) {
1699             this.mVelocity = mVelocity;
1700         }
1701 
setStartState(int startState)1702         public void setStartState(int startState) {
1703             this.mStartState = startState;
1704         }
1705 
recordState()1706         public void recordState() {
1707             mEndState = MotionLayout.this.mEndState;
1708             mStartState = MotionLayout.this.mBeginState;
1709             mVelocity = MotionLayout.this.getVelocity();
1710             mProgress = MotionLayout.this.getProgress();
1711         }
1712     }
1713 
1714     /**
1715      * Set the transition state as a bundle
1716      */
setTransitionState(Bundle bundle)1717     public void setTransitionState(Bundle bundle) {
1718         if (mStateCache == null) {
1719             mStateCache = new StateCache();
1720         }
1721         mStateCache.setTransitionState(bundle);
1722         if (isAttachedToWindow()) {
1723             mStateCache.apply();
1724         }
1725     }
1726 
1727     /**
1728      * @return bundle containing start and end state
1729      */
getTransitionState()1730     public Bundle getTransitionState() {
1731         if (mStateCache == null) {
1732             mStateCache = new StateCache();
1733         }
1734         mStateCache.recordState();
1735         return mStateCache.getTransitionState();
1736     }
1737 
1738     /**
1739      * Set the transition position between 0 an 1
1740      *
1741      * @param pos the position in the transition from 0...1
1742      */
setProgress(float pos)1743     public void setProgress(float pos) {
1744         if (pos < 0.0f || pos > 1.0f) {
1745             Log.w(TAG, "Warning! Progress is defined for values between 0.0 and 1.0 inclusive");
1746         }
1747         if (!isAttachedToWindow()) {
1748             if (mStateCache == null) {
1749                 mStateCache = new StateCache();
1750             }
1751             mStateCache.setProgress(pos);
1752             return;
1753         }
1754         if (DEBUG) {
1755             String str = getContext().getResources().getResourceName(mBeginState) + " -> ";
1756             str += getContext().getResources().getResourceName(mEndState) + ":" + getProgress();
1757             Log.v(TAG, Debug.getLocation() + " > " + str);
1758             Debug.logStack(TAG, " Progress = " + pos, 3);
1759         }
1760 
1761         if (pos <= 0f) {
1762             if (mTransitionLastPosition == 1.0f && mCurrentState == mEndState) {
1763                 setState(TransitionState.MOVING); // fire a transient moving as jumping start to end
1764             }
1765 
1766             mCurrentState = mBeginState;
1767             if (mTransitionLastPosition == 0.0f) {
1768                 setState(TransitionState.FINISHED);
1769             }
1770         } else if (pos >= 1.0f) {
1771             if (mTransitionLastPosition == 0.0f && mCurrentState == mBeginState) {
1772                 setState(TransitionState.MOVING); // fire a transient moving as jumping end to start
1773             }
1774 
1775             mCurrentState = mEndState;
1776             if (mTransitionLastPosition == 1.0f) {
1777                 setState(TransitionState.FINISHED);
1778             }
1779         } else {
1780             mCurrentState = UNSET;
1781             setState(TransitionState.MOVING);
1782         }
1783 
1784         if (mScene == null) {
1785             return;
1786         }
1787 
1788         mTransitionInstantly = true;
1789         mTransitionGoalPosition = pos;
1790         mTransitionPosition = pos;
1791         mTransitionLastTime = -1;
1792         mAnimationStartTime = -1;
1793         mInterpolator = null;
1794 
1795         mInTransition = true;
1796         invalidate();
1797     }
1798 
1799     /**
1800      * Create a transition view for every view
1801      */
setupMotionViews()1802     private void setupMotionViews() {
1803         int n = getChildCount();
1804 
1805         mModel.build();
1806         mInTransition = true;
1807         SparseArray<MotionController> controllers = new SparseArray<>();
1808         for (int i = 0; i < n; i++) {
1809             View child = getChildAt(i);
1810             controllers.put(child.getId(), mFrameArrayList.get(child));
1811         }
1812         int layoutWidth = getWidth();
1813         int layoutHeight = getHeight();
1814         int arc = mScene.gatPathMotionArc();
1815         if (arc != UNSET) {
1816             for (int i = 0; i < n; i++) {
1817                 MotionController motionController = mFrameArrayList.get(getChildAt(i));
1818                 if (motionController != null) {
1819                     motionController.setPathMotionArc(arc);
1820                 }
1821             }
1822         }
1823 
1824         SparseBooleanArray sparseBooleanArray = new SparseBooleanArray();
1825         int[] depends = new int[mFrameArrayList.size()];
1826         int count = 0;
1827         for (int i = 0; i < n; i++) {
1828             View view = getChildAt(i);
1829             MotionController motionController = mFrameArrayList.get(view);
1830             if (motionController.getAnimateRelativeTo() != UNSET) {
1831                 sparseBooleanArray.put(motionController.getAnimateRelativeTo(), true);
1832                 depends[count++] = motionController.getAnimateRelativeTo();
1833             }
1834         }
1835         if (mDecoratorsHelpers != null) {
1836             for (int i = 0; i < count; i++) {
1837                 MotionController motionController = mFrameArrayList.get(findViewById(depends[i]));
1838                 if (motionController == null) {
1839                     continue;
1840                 }
1841                 mScene.getKeyFrames(motionController);
1842             }
1843             // Allow helpers to access all the motionControllers after
1844             for (MotionHelper mDecoratorsHelper : mDecoratorsHelpers) {
1845                 mDecoratorsHelper.onPreSetup(this, mFrameArrayList);
1846             }
1847             for (int i = 0; i < count; i++) {
1848                 MotionController motionController = mFrameArrayList.get(findViewById(depends[i]));
1849                 if (motionController == null) {
1850                     continue;
1851                 }
1852                 motionController.setup(layoutWidth, layoutHeight,
1853                         mTransitionDuration, getNanoTime());
1854             }
1855         } else {
1856             for (int i = 0; i < count; i++) {
1857                 MotionController motionController = mFrameArrayList.get(findViewById(depends[i]));
1858                 if (motionController == null) {
1859                     continue;
1860                 }
1861                 mScene.getKeyFrames(motionController);
1862                 motionController.setup(layoutWidth, layoutHeight,
1863                         mTransitionDuration, getNanoTime());
1864             }
1865         }
1866         // getMap the KeyFrames for each view
1867         for (int i = 0; i < n; i++) {
1868             View v = getChildAt(i);
1869             MotionController motionController = mFrameArrayList.get(v);
1870             if (sparseBooleanArray.get(v.getId())) {
1871                 continue;
1872             }
1873 
1874             if (motionController != null) {
1875                 mScene.getKeyFrames(motionController);
1876                 motionController.setup(layoutWidth, layoutHeight,
1877                         mTransitionDuration, getNanoTime());
1878             }
1879         }
1880 
1881         float stagger = mScene.getStaggered();
1882         if (stagger != 0.0f) {
1883             boolean flip = stagger < 0.0;
1884             boolean useMotionStagger = false;
1885             stagger = Math.abs(stagger);
1886             float min = Float.MAX_VALUE, max = -Float.MAX_VALUE;
1887             for (int i = 0; i < n; i++) {
1888                 MotionController f = mFrameArrayList.get(getChildAt(i));
1889                 if (!Float.isNaN(f.mMotionStagger)) {
1890                     useMotionStagger = true;
1891                     break;
1892                 }
1893                 float x = f.getFinalX();
1894                 float y = f.getFinalY();
1895                 float mdist = flip ? (y - x) : (y + x);
1896                 min = Math.min(min, mdist);
1897                 max = Math.max(max, mdist);
1898             }
1899             if (useMotionStagger) {
1900                 min = Float.MAX_VALUE;
1901                 max = -Float.MAX_VALUE;
1902 
1903                 for (int i = 0; i < n; i++) {
1904                     MotionController f = mFrameArrayList.get(getChildAt(i));
1905                     if (!Float.isNaN(f.mMotionStagger)) {
1906                         min = Math.min(min, f.mMotionStagger);
1907                         max = Math.max(max, f.mMotionStagger);
1908                     }
1909                 }
1910                 for (int i = 0; i < n; i++) {
1911                     MotionController f = mFrameArrayList.get(getChildAt(i));
1912                     if (!Float.isNaN(f.mMotionStagger)) {
1913 
1914                         f.mStaggerScale = 1 / (1 - stagger);
1915                         if (flip) {
1916                             f.mStaggerOffset = stagger - stagger
1917                                     * ((max - f.mMotionStagger) / (max - min));
1918                         } else {
1919                             f.mStaggerOffset = stagger - stagger
1920                                     * (f.mMotionStagger - min) / (max - min);
1921                         }
1922                     }
1923                 }
1924             } else {
1925                 for (int i = 0; i < n; i++) {
1926                     MotionController f = mFrameArrayList.get(getChildAt(i));
1927                     float x = f.getFinalX();
1928                     float y = f.getFinalY();
1929                     float mdist = flip ? (y - x) : (y + x);
1930                     f.mStaggerScale = 1 / (1 - stagger);
1931                     f.mStaggerOffset = stagger - stagger * (mdist - min) / (max - min);
1932                 }
1933             }
1934         }
1935     }
1936 
1937     /**
1938      * @param touchUpMode     behavior on touch up, can be either:
1939      *                        <ul>
1940      *                        <li>TOUCH_UP_COMPLETE (default) : will complete the transition,
1941      *                        picking up
1942      *                        automatically a correct velocity to do so</li>
1943      *                        <li>TOUCH_UP_STOP : will allow stopping mid-transition</li>
1944      *                        <li>TOUCH_UP_DECELERATE : will slowly decay,
1945      *                        possibly past the transition (i.e.
1946      *                        it will do a hard stop if unmanaged)</li>
1947      *                        <li>TOUCH_UP_DECELERATE_AND_COMPLETE :
1948      *                        will automatically pick between
1949      *                        TOUCH_UP_COMPLETE and TOUCH_UP_DECELERATE</li>
1950      *                        </ul>,
1951      *                        TOUCH_UP_STOP (will allow stopping
1952      * @param position        animate to given position
1953      * @param currentVelocity
1954      */
1955     public void touchAnimateTo(int touchUpMode, float position, float currentVelocity) {
1956         if (DEBUG) {
1957             Log.v(TAG, " " + Debug.getLocation() + " touchAnimateTo "
1958                     + position + "   " + currentVelocity);
1959         }
1960         if (mScene == null) {
1961             return;
1962         }
1963         if (mTransitionLastPosition == position) {
1964             return;
1965         }
1966 
1967         mTemporalInterpolator = true;
1968         mAnimationStartTime = getNanoTime();
1969         mTransitionDuration = mScene.getDuration() / 1000f;
1970 
1971         mTransitionGoalPosition = position;
1972         mInTransition = true;
1973 
1974         switch (touchUpMode) {
1975             case TOUCH_UP_COMPLETE:
1976             case TOUCH_UP_NEVER_TO_START:
1977             case TOUCH_UP_NEVER_TO_END:
1978             case TOUCH_UP_COMPLETE_TO_START:
1979             case TOUCH_UP_COMPLETE_TO_END: {
1980                 if (touchUpMode == TOUCH_UP_COMPLETE_TO_START
1981                         || touchUpMode == TOUCH_UP_NEVER_TO_END) {
1982                     position = 0;
1983                 } else if (touchUpMode == TOUCH_UP_COMPLETE_TO_END
1984                         || touchUpMode == TOUCH_UP_NEVER_TO_START) {
1985                     position = 1;
1986                 }
1987 
1988                 if (mScene.getAutoCompleteMode()
1989                         == TouchResponse.COMPLETE_MODE_CONTINUOUS_VELOCITY) {
1990                     mStopLogic.config(mTransitionLastPosition, position, currentVelocity,
1991                             mTransitionDuration, mScene.getMaxAcceleration(),
1992                             mScene.getMaxVelocity());
1993                 } else {
1994                     mStopLogic.springConfig(mTransitionLastPosition, position, currentVelocity,
1995                             mScene.getSpringMass(),
1996                             mScene.getSpringStiffiness(),
1997                             mScene.getSpringDamping(),
1998                             mScene.getSpringStopThreshold(), mScene.getSpringBoundary());
1999                 }
2000 
2001                 int currentState = mCurrentState; // TODO: remove setProgress(), temporary fix
2002                 mTransitionGoalPosition = position;
2003                 mCurrentState = currentState;
2004                 mInterpolator = mStopLogic;
2005             }
2006                 break;
2007             case TOUCH_UP_STOP: {
2008                 // nothing to do
2009             }
2010             break;
2011             case TOUCH_UP_DECELERATE: {
2012                 mDecelerateLogic.config(currentVelocity, mTransitionLastPosition,
2013                         mScene.getMaxAcceleration());
2014                 mInterpolator = mDecelerateLogic;
2015             }
2016             break;
2017             case TOUCH_UP_DECELERATE_AND_COMPLETE: {
2018                 if (willJump(currentVelocity, mTransitionLastPosition,
2019                         mScene.getMaxAcceleration())) {
2020                     mDecelerateLogic.config(currentVelocity,
2021                             mTransitionLastPosition, mScene.getMaxAcceleration());
2022                     mInterpolator = mDecelerateLogic;
2023                 } else {
2024                     mStopLogic.config(mTransitionLastPosition, position, currentVelocity,
2025                             mTransitionDuration,
2026                             mScene.getMaxAcceleration(), mScene.getMaxVelocity());
2027                     mLastVelocity = 0;
2028                     int currentState = mCurrentState; // TODO: remove setProgress(), (temporary fix)
2029                     mTransitionGoalPosition = position;
2030                     mCurrentState = currentState;
2031                     mInterpolator = mStopLogic;
2032                 }
2033             }
2034             break;
2035         }
2036 
2037         mTransitionInstantly = false;
2038         mAnimationStartTime = getNanoTime();
2039         invalidate();
2040     }
2041 
2042     /**
2043      * Allows you to use trigger spring motion touch behaviour.
2044      * You must have configured all the spring parameters in the Transition's OnSwipe
2045      *
2046      * @param position the position 0 - 1
2047      * @param currentVelocity the current velocity rate of change in position per second
2048      */
2049     public void touchSpringTo(float position, float currentVelocity) {
2050         if (DEBUG) {
2051             Log.v(TAG, " " + Debug.getLocation()
2052                     + " touchAnimateTo " + position + "   " + currentVelocity);
2053         }
2054         if (mScene == null) {
2055             return;
2056         }
2057         if (mTransitionLastPosition == position) {
2058             return;
2059         }
2060 
2061         mTemporalInterpolator = true;
2062         mAnimationStartTime = getNanoTime();
2063         mTransitionDuration = mScene.getDuration() / 1000f;
2064 
2065         mTransitionGoalPosition = position;
2066         mInTransition = true;
2067 
2068         mStopLogic.springConfig(mTransitionLastPosition, position, currentVelocity,
2069                 mScene.getSpringMass(), mScene.getSpringStiffiness(), mScene.getSpringDamping(),
2070                 mScene.getSpringStopThreshold(), mScene.getSpringBoundary());
2071 
2072         int currentState = mCurrentState; // TODO: remove setProgress(), this is a temporary fix
2073         mTransitionGoalPosition = position;
2074         mCurrentState = currentState;
2075         mInterpolator = mStopLogic;
2076 
2077 
2078         mTransitionInstantly = false;
2079         mAnimationStartTime = getNanoTime();
2080         invalidate();
2081     }
2082 
2083     private static boolean willJump(float velocity,
2084                                     float position,
2085                                     float maxAcceleration) {
2086         if (velocity > 0) {
2087             float time = velocity / maxAcceleration;
2088             float pos = velocity * time - (maxAcceleration * time * time) / 2;
2089             return (position + pos > 1);
2090         } else {
2091             float time = -velocity / maxAcceleration;
2092             float pos = velocity * time + (maxAcceleration * time * time) / 2;
2093             return (position + pos < 0);
2094         }
2095     }
2096 
2097     /**
2098      * Basic deceleration interpolator
2099      */
2100     class DecelerateInterpolator extends MotionInterpolator {
2101         float mInitialV = 0;
2102         float mCurrentP = 0;
2103         float mMaxA;
2104 
2105         public void config(float velocity, float position, float maxAcceleration) {
2106             mInitialV = velocity;
2107             mCurrentP = position;
2108             mMaxA = maxAcceleration;
2109         }
2110 
2111         @Override
2112         public float getInterpolation(float time) {
2113             if (mInitialV > 0) {
2114                 if (mInitialV / mMaxA < time) {
2115                     time = mInitialV / mMaxA;
2116                 }
2117                 mLastVelocity = mInitialV - mMaxA * time;
2118                 float pos = mInitialV * time - (mMaxA * time * time) / 2;
2119                 return pos + mCurrentP;
2120             } else {
2121 
2122                 if (-mInitialV / mMaxA < time) {
2123                     time = -mInitialV / mMaxA;
2124                 }
2125                 mLastVelocity = mInitialV + mMaxA * time;
2126                 float pos = mInitialV * time + (mMaxA * time * time) / 2;
2127                 return pos + mCurrentP;
2128             }
2129         }
2130 
2131         @Override
2132         public float getVelocity() {
2133             return mLastVelocity;
2134         }
2135     }
2136 
2137     /**
2138      * @param position animate to given position
2139      */
2140     void animateTo(float position) {
2141         if (DEBUG) {
2142             Log.v(TAG, " " + Debug.getLocation() + " ... animateTo(" + position
2143                     + ") last:" + mTransitionLastPosition);
2144         }
2145         if (mScene == null) {
2146             return;
2147         }
2148 
2149         if (mTransitionLastPosition != mTransitionPosition && mTransitionInstantly) {
2150             // if we had a call from setProgress() but evaluate() didn't run,
2151             // the mTransitionLastPosition might not have been updated
2152             mTransitionLastPosition = mTransitionPosition;
2153         }
2154 
2155         if (mTransitionLastPosition == position) {
2156             return;
2157         }
2158         mTemporalInterpolator = false;
2159         float currentPosition = mTransitionLastPosition;
2160         mTransitionGoalPosition = position;
2161         mTransitionDuration = mScene.getDuration() / 1000f;
2162         setProgress(mTransitionGoalPosition);
2163         mInterpolator = null;
2164         mProgressInterpolator = mScene.getInterpolator();
2165         mTransitionInstantly = false;
2166         mAnimationStartTime = getNanoTime();
2167         mInTransition = true;
2168         mTransitionPosition = currentPosition;
2169         if (DEBUG) {
2170             Log.v(TAG, Debug.getLocation() + " mTransitionLastPosition = "
2171                     + mTransitionLastPosition + " currentPosition =" + currentPosition);
2172         }
2173         mTransitionLastPosition = currentPosition;
2174         invalidate();
2175     }
2176 
2177     private void computeCurrentPositions() {
2178         final int n = getChildCount();
2179         for (int i = 0; i < n; i++) {
2180             View v = getChildAt(i);
2181             MotionController frame = mFrameArrayList.get(v);
2182             if (frame == null) {
2183                 continue;
2184             }
2185             frame.setStartCurrentState(v);
2186         }
2187     }
2188 
2189     /**
2190      * Animate to the starting position of the current transition.
2191      * This will not work during on create as there is no transition
2192      * Transitions are only set up during onAttach
2193      */
2194     public void transitionToStart() {
2195         animateTo(0.0f);
2196     }
2197 
2198     /**
2199      * Animate to the starting position of the current transition.
2200      * This will not work during on create as there is no transition
2201      * Transitions are only set up during onAttach
2202      *
2203      * @param onComplete callback when task is done
2204      */
2205     public void transitionToStart(Runnable onComplete) {
2206         animateTo(0.0f);
2207         mOnComplete = onComplete;
2208     }
2209 
2210     /**
2211      * Animate to the ending position of the current transition.
2212      * This will not work during on create as there is no transition
2213      * Transitions are only set up during onAttach
2214      */
2215     public void transitionToEnd() {
2216         animateTo(1.0f);
2217         mOnComplete = null;
2218     }
2219 
2220     /**
2221      * Animate to the ending position of the current transition.
2222      * This will not work during on create as there is no transition
2223      * Transitions are only set up during onAttach
2224      *
2225      * @param onComplete callback when task is done
2226      */
2227     public void transitionToEnd(Runnable onComplete) {
2228         animateTo(1.0f);
2229         mOnComplete = onComplete;
2230     }
2231 
2232     /**
2233      * Animate to the state defined by the id.
2234      * The id is the id of the ConstraintSet or the id of the State.
2235      *
2236      * @param id the state to transition to
2237      */
2238     public void transitionToState(int id) {
2239         if (!isAttachedToWindow()) {
2240             if (mStateCache == null) {
2241                 mStateCache = new StateCache();
2242             }
2243             mStateCache.setEndState(id);
2244             return;
2245         }
2246         transitionToState(id, -1, -1);
2247     }
2248 
2249     /**
2250      * Animate to the state defined by the id.
2251      * The id is the id of the ConstraintSet or the id of the State.
2252      *
2253      * @param id       the state to transition to
2254      * @param duration time in ms. if 0 set by default or transition -1 by current
2255      */
2256 
2257     public void transitionToState(int id, int duration) {
2258         if (!isAttachedToWindow()) {
2259             if (mStateCache == null) {
2260                 mStateCache = new StateCache();
2261             }
2262             mStateCache.setEndState(id);
2263             return;
2264         }
2265         transitionToState(id, -1, -1, duration);
2266     }
2267 
2268     /**
2269      * Animate to the state defined by the id.
2270      * Width and height may be used in the picking of the id using this StateSet.
2271      *
2272      * @param id           the state to transition
2273      * @param screenWidth  the with of the motionLayout used to select the variant
2274      * @param screenHeight the height of the motionLayout used to select the variant
2275      */
2276     public void transitionToState(int id, int screenWidth, int screenHeight) {
2277         transitionToState(id, screenWidth, screenHeight, -1);
2278     }
2279 
2280     /**
2281      * Rotate the layout based on the angle to a ConstraintSet
2282      * @param id constraintSet
2283      * @param duration time to take to rotate
2284      */
2285     public void rotateTo(int id, int duration) {
2286         mInRotation = true;
2287         mPreRotateWidth = getWidth();
2288         mPreRotateHeight = getHeight();
2289 
2290         int currentRotation = getDisplay().getRotation();
2291         mRotatMode = (((currentRotation + 1) % 4) > ((mPreviouseRotation + 1) % 4)) ? 1 : 2;
2292 
2293         mPreviouseRotation = currentRotation;
2294         final int n = getChildCount();
2295         for (int i = 0; i < n; i++) {
2296             View v = getChildAt(i);
2297             ViewState bounds = mPreRotate.get(v);
2298             if (bounds == null) {
2299                 bounds = new ViewState();
2300                 mPreRotate.put(v, bounds);
2301             }
2302             bounds.getState(v);
2303         }
2304 
2305         mBeginState = -1;
2306         mEndState = id;
2307         mScene.setTransition(-1, mEndState);
2308         mModel.initFrom(mLayoutWidget, null, mScene.getConstraintSet(mEndState));
2309         mTransitionPosition = 0;
2310 
2311         mTransitionLastPosition = 0;
2312         invalidate();
2313         transitionToEnd(new Runnable() {
2314             @Override
2315             public void run() {
2316                 mInRotation = false;
2317             }
2318         });
2319         if (duration > 0) {
2320             mTransitionDuration = duration / 1000f;
2321         }
2322 
2323     }
2324 
2325     public boolean isInRotation() {
2326         return mInRotation;
2327     }
2328 
2329     /**
2330      * This jumps to a state
2331      * It will be at that state after one repaint cycle
2332      * If the current transition contains that state.
2333      * It setsProgress 0 or 1 to that state.
2334      * If not in the current transition itsl
2335      *
2336      * @param id state to set
2337      */
2338     public void jumpToState(int id) {
2339         if (!isAttachedToWindow()) {
2340             mCurrentState = id;
2341         }
2342         if (mBeginState == id) {
2343             setProgress(0);
2344         } else if (mEndState == id) {
2345             setProgress(1);
2346         } else {
2347             setTransition(id, id);
2348         }
2349     }
2350 
2351     /**
2352      * Animate to the state defined by the id.
2353      * Width and height may be used in the picking of the id using this StateSet.
2354      *
2355      * @param id           the state to transition
2356      * @param screenWidth  the with of the motionLayout used to select the variant
2357      * @param screenHeight the height of the motionLayout used to select the variant
2358      * @param duration     time in ms. if 0 set by default or transition -1 by current
2359      */
2360 
2361     public void transitionToState(int id, int screenWidth, int screenHeight, int duration) {
2362         // if id is either end or start state, transition using current setup.
2363         // if id is not part of end/start, need to setup
2364 
2365         // if id == end state, just animate
2366         // ... but check if currentState is unknown. if unknown, call computeCurrentPosition
2367         // if id != end state
2368         if (DEBUG && mScene.mStateSet == null) {
2369             Log.v(TAG, Debug.getLocation() + " mStateSet = null");
2370         }
2371         if (mScene != null && mScene.mStateSet != null) {
2372             int tmp_id = mScene.mStateSet.convertToConstraintSet(mCurrentState,
2373                     id, screenWidth, screenHeight);
2374 
2375             if (tmp_id != -1) {
2376                 if (DEBUG) {
2377                     Log.v(TAG, " got state  " + Debug.getLocation() + " lookup("
2378                             + Debug.getName(getContext(), id)
2379                             + screenWidth + " , " + screenHeight + " ) =  "
2380                             + Debug.getName(getContext(), tmp_id));
2381                 }
2382                 id = tmp_id;
2383             }
2384         }
2385         if (mCurrentState == id) {
2386             return;
2387         }
2388         if (mBeginState == id) {
2389             animateTo(0.0f);
2390             if (duration > 0) {
2391                 mTransitionDuration = duration / 1000f;
2392             }
2393             return;
2394         }
2395         if (mEndState == id) {
2396             animateTo(1.0f);
2397             if (duration > 0) {
2398                 mTransitionDuration = duration / 1000f;
2399             }
2400             return;
2401         }
2402         mEndState = id;
2403         if (mCurrentState != UNSET) {
2404             if (DEBUG) {
2405                 Log.v(TAG, " transitionToState " + Debug.getLocation() + " current  = "
2406                         + Debug.getName(getContext(), mCurrentState)
2407                         + " to " + Debug.getName(getContext(), mEndState));
2408                 Debug.logStack(TAG, " transitionToState  ", 4);
2409                 Log.v(TAG, "-------------------------------------------");
2410             }
2411             setTransition(mCurrentState, id);
2412 
2413             animateTo(1.0f);
2414 
2415             mTransitionLastPosition = 0;
2416             transitionToEnd();
2417             if (duration > 0) {
2418                 mTransitionDuration = duration / 1000f;
2419             }
2420             return;
2421         }
2422         if (DEBUG) {
2423             Log.v(TAG, "setTransition  unknown -> "
2424                     + Debug.getName(getContext(), id));
2425         }
2426 
2427         // TODO correctly use width & height
2428         mTemporalInterpolator = false;
2429         mTransitionGoalPosition = 1;
2430         mTransitionPosition = 0;
2431         mTransitionLastPosition = 0;
2432         mTransitionLastTime = getNanoTime();
2433         mAnimationStartTime = getNanoTime();
2434         mTransitionInstantly = false;
2435         mInterpolator = null;
2436         if (duration == -1) {
2437             mTransitionDuration = mScene.getDuration() / 1000f;
2438         }
2439         mBeginState = UNSET;
2440         mScene.setTransition(mBeginState, mEndState);
2441         SparseArray<MotionController> controllers = new SparseArray<>();
2442         if (duration == 0) {
2443             mTransitionDuration = mScene.getDuration() / 1000f;
2444         } else if (duration > 0) {
2445             mTransitionDuration = duration / 1000f;
2446         }
2447 
2448         int n = getChildCount();
2449 
2450         mFrameArrayList.clear();
2451         for (int i = 0; i < n; i++) {
2452             View v = getChildAt(i);
2453             MotionController f = new MotionController(v);
2454             mFrameArrayList.put(v, f);
2455             controllers.put(v.getId(), mFrameArrayList.get(v));
2456         }
2457         mInTransition = true;
2458 
2459         mModel.initFrom(mLayoutWidget, null, mScene.getConstraintSet(id));
2460         rebuildScene();
2461         mModel.build();
2462         computeCurrentPositions();
2463         int layoutWidth = getWidth();
2464         int layoutHeight = getHeight();
2465         // getMap the KeyFrames for each view
2466 
2467         if (mDecoratorsHelpers != null) {
2468             for (int i = 0; i < n; i++) {
2469                 MotionController motionController = mFrameArrayList.get(getChildAt(i));
2470                 if (motionController == null) {
2471                     continue;
2472                 }
2473                 mScene.getKeyFrames(motionController);
2474             }
2475             // Allow helpers to access all the motionControllers after
2476             for (MotionHelper mDecoratorsHelper : mDecoratorsHelpers) {
2477                 mDecoratorsHelper.onPreSetup(this, mFrameArrayList);
2478             }
2479             for (int i = 0; i < n; i++) {
2480                 MotionController motionController = mFrameArrayList.get(getChildAt(i));
2481                 if (motionController == null) {
2482                     continue;
2483                 }
2484                 motionController.setup(layoutWidth, layoutHeight,
2485                         mTransitionDuration, getNanoTime());
2486             }
2487         } else {
2488             for (int i = 0; i < n; i++) {
2489                 MotionController motionController = mFrameArrayList.get(getChildAt(i));
2490                 if (motionController == null) {
2491                     continue;
2492                 }
2493                 mScene.getKeyFrames(motionController);
2494                 motionController.setup(layoutWidth, layoutHeight,
2495                         mTransitionDuration, getNanoTime());
2496             }
2497         }
2498 
2499         float stagger = mScene.getStaggered();
2500         if (stagger != 0.0f) {
2501             float min = Float.MAX_VALUE, max = -Float.MAX_VALUE;
2502             for (int i = 0; i < n; i++) {
2503                 MotionController f = mFrameArrayList.get(getChildAt(i));
2504                 float x = f.getFinalX();
2505                 float y = f.getFinalY();
2506                 min = Math.min(min, y + x);
2507                 max = Math.max(max, y + x);
2508             }
2509 
2510             for (int i = 0; i < n; i++) {
2511                 MotionController f = mFrameArrayList.get(getChildAt(i));
2512                 float x = f.getFinalX();
2513                 float y = f.getFinalY();
2514                 f.mStaggerScale = 1 / (1 - stagger);
2515                 f.mStaggerOffset = stagger - stagger * (x + y - min) / (max - min);
2516             }
2517         }
2518 
2519         mTransitionPosition = 0;
2520         mTransitionLastPosition = 0;
2521         mInTransition = true;
2522 
2523         invalidate();
2524     }
2525 
2526     /**
2527      * Returns the last velocity used in the transition
2528      *
2529      * @return
2530      */
2531     public float getVelocity() {
2532         return mLastVelocity;
2533     }
2534 
2535     /**
2536      * Returns the last layout velocity used in the transition
2537      *
2538      * @param view           The view
2539      * @param posOnViewX     The x position on the view
2540      * @param posOnViewY     The y position on the view
2541      * @param returnVelocity The velocity
2542      * @param type           Velocity returned 0 = post layout, 1 = layout, 2 = static postlayout
2543      */
2544     public void getViewVelocity(View view,
2545                                 float posOnViewX,
2546                                 float posOnViewY,
2547                                 float[] returnVelocity,
2548                                 int type) {
2549         float v = mLastVelocity;
2550         float position = mTransitionLastPosition;
2551         if (mInterpolator != null) {
2552             float deltaT = EPSILON;
2553             float dir = Math.signum(mTransitionGoalPosition - mTransitionLastPosition);
2554             float interpolatedPosition =
2555                     mInterpolator.getInterpolation(mTransitionLastPosition + deltaT);
2556             position = mInterpolator.getInterpolation(mTransitionLastPosition);
2557             interpolatedPosition -= position;
2558             interpolatedPosition /= deltaT;
2559             v = dir * interpolatedPosition / mTransitionDuration;
2560         }
2561 
2562         if (mInterpolator instanceof MotionInterpolator) {
2563             v = ((MotionInterpolator) mInterpolator).getVelocity();
2564 
2565         }
2566 
2567         MotionController f = mFrameArrayList.get(view);
2568         if ((type & 1) == 0) {
2569             f.getPostLayoutDvDp(position,
2570                     view.getWidth(), view.getHeight(),
2571                     posOnViewX, posOnViewY, returnVelocity);
2572         } else {
2573             f.getDpDt(position, posOnViewX, posOnViewY, returnVelocity);
2574         }
2575         if (type < VELOCITY_STATIC_POST_LAYOUT) {
2576             returnVelocity[0] *= v;
2577             returnVelocity[1] *= v;
2578         }
2579 
2580     }
2581 
2582     ////////////////////////////////////////////////////////////////////////////////
2583     // This contains the logic for interacting with the ConstraintLayout Solver
2584     class Model {
2585         ConstraintWidgetContainer mLayoutStart = new ConstraintWidgetContainer();
2586         ConstraintWidgetContainer mLayoutEnd = new ConstraintWidgetContainer();
2587         ConstraintSet mStart = null;
2588         ConstraintSet mEnd = null;
2589         int mStartId;
2590         int mEndId;
2591 
2592         void copy(ConstraintWidgetContainer src, ConstraintWidgetContainer dest) {
2593             ArrayList<ConstraintWidget> children = src.getChildren();
2594             HashMap<ConstraintWidget, ConstraintWidget> map = new HashMap<>();
2595             map.put(src, dest);
2596             dest.getChildren().clear();
2597             dest.copy(src, map);
2598             for (ConstraintWidget child_s : children) {
2599                 ConstraintWidget child_d;
2600                 if (child_s instanceof androidx.constraintlayout.core.widgets.Barrier) {
2601                     child_d = new androidx.constraintlayout.core.widgets.Barrier();
2602                 } else if (child_s instanceof androidx.constraintlayout.core.widgets.Guideline) {
2603                     child_d = new androidx.constraintlayout.core.widgets.Guideline();
2604                 } else if (child_s instanceof Flow) {
2605                     child_d = new Flow();
2606                 } else if (child_s instanceof Placeholder) {
2607                     child_d = new Placeholder();
2608                 } else if (child_s instanceof androidx.constraintlayout.core.widgets.Helper) {
2609                     child_d = new androidx.constraintlayout.core.widgets.HelperWidget();
2610                 } else {
2611                     child_d = new ConstraintWidget();
2612                 }
2613                 dest.add(child_d);
2614                 map.put(child_s, child_d);
2615             }
2616             for (ConstraintWidget child_s : children) {
2617                 map.get(child_s).copy(child_s, map);
2618             }
2619         }
2620 
2621         void initFrom(ConstraintWidgetContainer baseLayout,
2622                       ConstraintSet start,
2623                       ConstraintSet end) {
2624             mStart = start;
2625             mEnd = end;
2626             mLayoutStart =  new ConstraintWidgetContainer();
2627             mLayoutEnd =  new ConstraintWidgetContainer();
2628             mLayoutStart.setMeasurer(mLayoutWidget.getMeasurer());
2629             mLayoutEnd.setMeasurer(mLayoutWidget.getMeasurer());
2630             mLayoutStart.removeAllChildren();
2631             mLayoutEnd.removeAllChildren();
2632             copy(mLayoutWidget, mLayoutStart);
2633             copy(mLayoutWidget, mLayoutEnd);
2634             if (mTransitionLastPosition > 0.5) {
2635                 if (start != null) {
2636                     setupConstraintWidget(mLayoutStart, start);
2637                 }
2638                 setupConstraintWidget(mLayoutEnd, end);
2639             } else {
2640                 setupConstraintWidget(mLayoutEnd, end);
2641                 if (start != null) {
2642                     setupConstraintWidget(mLayoutStart, start);
2643                 }
2644             }
2645             // then init the engine...
2646             if (DEBUG) {
2647                 Log.v(TAG, "> mLayoutStart.updateHierarchy " + Debug.getLocation());
2648             }
2649             mLayoutStart.setRtl(isRtl());
2650             mLayoutStart.updateHierarchy();
2651 
2652             if (DEBUG) {
2653                 for (ConstraintWidget child : mLayoutStart.getChildren()) {
2654                     View view = (View) child.getCompanionWidget();
2655                     debugWidget(">>>>>>>  " + Debug.getName(view), child);
2656                 }
2657                 Log.v(TAG, "> mLayoutEnd.updateHierarchy " + Debug.getLocation());
2658                 Log.v(TAG, "> mLayoutEnd.updateHierarchy  "
2659                         + Debug.getLocation() + "  == isRtl()=" + isRtl());
2660             }
2661             mLayoutEnd.setRtl(isRtl());
2662             mLayoutEnd.updateHierarchy();
2663 
2664             if (DEBUG) {
2665                 for (ConstraintWidget child : mLayoutEnd.getChildren()) {
2666                     View view = (View) child.getCompanionWidget();
2667                     debugWidget(">>>>>>>  " + Debug.getName(view), child);
2668                 }
2669             }
2670             ViewGroup.LayoutParams layoutParams = getLayoutParams();
2671             if (layoutParams != null) {
2672                 if (layoutParams.width == WRAP_CONTENT) {
2673                     mLayoutStart.setHorizontalDimensionBehaviour(
2674                             ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
2675                     mLayoutEnd.setHorizontalDimensionBehaviour(
2676                             ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
2677                 }
2678                 if (layoutParams.height == WRAP_CONTENT) {
2679                     mLayoutStart.setVerticalDimensionBehaviour(
2680                             ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
2681                     mLayoutEnd.setVerticalDimensionBehaviour(
2682                             ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
2683                 }
2684             }
2685         }
2686 
2687         private void setupConstraintWidget(ConstraintWidgetContainer base, ConstraintSet cSet) {
2688             SparseArray<ConstraintWidget> mapIdToWidget = new SparseArray<>();
2689             Constraints.LayoutParams layoutParams =
2690                     new Constraints.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
2691                             ViewGroup.LayoutParams.WRAP_CONTENT);
2692 
2693             mapIdToWidget.clear();
2694             mapIdToWidget.put(PARENT_ID, base);
2695             mapIdToWidget.put(getId(), base);
2696             if (cSet != null && cSet.mRotate != 0) {
2697                 resolveSystem(mLayoutEnd, getOptimizationLevel(),
2698                         MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY),
2699                         MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY));
2700             }
2701             //  build id widget map
2702             for (ConstraintWidget child : base.getChildren()) {
2703                 child.setAnimated(true);
2704                 View view = (View) child.getCompanionWidget();
2705                 mapIdToWidget.put(view.getId(), child);
2706             }
2707 
2708             for (ConstraintWidget child : base.getChildren()) {
2709                 View view = (View) child.getCompanionWidget();
2710                 cSet.applyToLayoutParams(view.getId(), layoutParams);
2711 
2712                 child.setWidth(cSet.getWidth(view.getId()));
2713                 child.setHeight(cSet.getHeight(view.getId()));
2714                 if (view instanceof ConstraintHelper) {
2715                     cSet.applyToHelper((ConstraintHelper) view, child, layoutParams, mapIdToWidget);
2716                     if (view instanceof Barrier) {
2717                         ((Barrier) view).validateParams();
2718                         if (DEBUG) {
2719                             Log.v(TAG, ">>>>>>>>>> Barrier " + Debug.getName(getContext(),
2720                                     ((Barrier) view).getReferencedIds()));
2721                         }
2722                     }
2723                 }
2724                 if (DEBUG) {
2725                     debugLayoutParam(">>>>>>>  " + Debug.getName(view), layoutParams);
2726                 }
2727                 layoutParams.resolveLayoutDirection(getLayoutDirection());
2728                 applyConstraintsFromLayoutParams(false, view, child, layoutParams, mapIdToWidget);
2729                 if (cSet.getVisibilityMode(view.getId()) == ConstraintSet.VISIBILITY_MODE_IGNORE) {
2730                     child.setVisibility(view.getVisibility());
2731                 } else {
2732                     child.setVisibility(cSet.getVisibility(view.getId()));
2733                 }
2734             }
2735             for (ConstraintWidget child : base.getChildren()) {
2736                 if (child instanceof androidx.constraintlayout.core.widgets.VirtualLayout) {
2737                     ConstraintHelper view = (ConstraintHelper) child.getCompanionWidget();
2738                     Helper helper = (Helper) child;
2739                     view.updatePreLayout(base, helper, mapIdToWidget);
2740                     androidx.constraintlayout.core.widgets.VirtualLayout virtualLayout =
2741                             (androidx.constraintlayout.core.widgets.VirtualLayout) helper;
2742                     virtualLayout.captureWidgets();
2743                 }
2744             }
2745         }
2746 
2747         ConstraintWidget getWidget(ConstraintWidgetContainer container, View view) {
2748             if (container.getCompanionWidget() == view) {
2749                 return container;
2750             }
2751             ArrayList<ConstraintWidget> children = container.getChildren();
2752             final int count = children.size();
2753             for (int i = 0; i < count; i++) {
2754                 ConstraintWidget widget = children.get(i);
2755                 if (widget.getCompanionWidget() == view) {
2756                     return widget;
2757                 }
2758 
2759             }
2760             return null;
2761         }
2762 
2763         @SuppressLint("LogConditional")
2764         private void debugLayoutParam(String str, LayoutParams params) {
2765             String a = " ";
2766             a += params.startToStart != UNSET ? "SS" : "__";
2767             a += params.startToEnd != UNSET ? "|SE" : "|__";
2768             a += params.endToStart != UNSET ? "|ES" : "|__";
2769             a += params.endToEnd != UNSET ? "|EE" : "|__";
2770             a += params.leftToLeft != UNSET ? "|LL" : "|__";
2771             a += params.leftToRight != UNSET ? "|LR" : "|__";
2772             a += params.rightToLeft != UNSET ? "|RL" : "|__";
2773             a += params.rightToRight != UNSET ? "|RR" : "|__";
2774             a += params.topToTop != UNSET ? "|TT" : "|__";
2775             a += params.topToBottom != UNSET ? "|TB" : "|__";
2776             a += params.bottomToTop != UNSET ? "|BT" : "|__";
2777             a += params.bottomToBottom != UNSET ? "|BB" : "|__";
2778             Log.v(TAG, str + a);
2779         }
2780 
2781         @SuppressLint("LogConditional")
2782         private void debugWidget(String str, ConstraintWidget child) {
2783             String a = " ";
2784             a += child.mTop.mTarget != null
2785                     ? ("T" + (child.mTop.mTarget.mType == ConstraintAnchor.Type.TOP ? "T" : "B"))
2786                     : "__";
2787             a += child.mBottom.mTarget != null
2788                     ? ("B" + (child.mBottom.mTarget.mType == ConstraintAnchor.Type.TOP ? "T" : "B"))
2789                     : "__";
2790             a += child.mLeft.mTarget != null
2791                     ? ("L" + (child.mLeft.mTarget.mType == ConstraintAnchor.Type.LEFT ? "L" : "R"))
2792                     : "__";
2793             a += child.mRight.mTarget != null
2794                     ? ("R" + (child.mRight.mTarget.mType == ConstraintAnchor.Type.LEFT ? "L" : "R"))
2795                     : "__";
2796             Log.v(TAG, str + a + " ---  " + child);
2797         }
2798 
2799         @SuppressLint("LogConditional")
2800         private void debugLayout(String title, ConstraintWidgetContainer c) {
2801             View v = (View) c.getCompanionWidget();
2802             String cName = title + " " + Debug.getName(v);
2803             Log.v(TAG, cName + "  ========= " + c);
2804             int count = c.getChildren().size();
2805             for (int i = 0; i < count; i++) {
2806                 String str = cName + "[" + i + "] ";
2807                 ConstraintWidget child = c.getChildren().get(i);
2808                 String a = "";
2809                 a += child.mTop.mTarget != null ? "T" : "_";
2810                 a += child.mBottom.mTarget != null ? "B" : "_";
2811                 a += child.mLeft.mTarget != null ? "L" : "_";
2812                 a += child.mRight.mTarget != null ? "R" : "_";
2813                 v = (View) child.getCompanionWidget();
2814                 String name = Debug.getName(v);
2815                 if (v instanceof TextView) {
2816                     name += "(" + ((TextView) v).getText() + ")";
2817                 }
2818                 Log.v(TAG, str + "  " + name + " " + child + " " + a);
2819             }
2820             Log.v(TAG, cName + " done. ");
2821         }
2822 
2823         public void reEvaluateState() {
2824             measure(mLastWidthMeasureSpec, mLastHeightMeasureSpec);
2825             setupMotionViews();
2826         }
2827 
2828         public void measure(int widthMeasureSpec, int heightMeasureSpec) {
2829             int widthMode = MeasureSpec.getMode(widthMeasureSpec);
2830             int heightMode = MeasureSpec.getMode(heightMeasureSpec);
2831 
2832             mWidthMeasureMode = widthMode;
2833             mHeightMeasureMode = heightMode;
2834 
2835             computeStartEndSize(widthMeasureSpec, heightMeasureSpec);
2836 
2837             // This works around the problem that MotionLayout calls its children
2838             // Wrap content children
2839             // with measure(AT_MOST,AT_MOST) then measure(EXACTLY, EXACTLY)
2840             // if a child of MotionLayout is a motionLayout
2841             // it would not know it could resize during animation
2842             // other Layouts may have this behaviour but for now this is the only one we support
2843 
2844             boolean recompute_start_end_size = true;
2845             if (getParent() instanceof MotionLayout
2846                     && widthMode == MeasureSpec.EXACTLY
2847                     && heightMode == MeasureSpec.EXACTLY) {
2848                 recompute_start_end_size = false;
2849             }
2850             if (recompute_start_end_size) {
2851                 computeStartEndSize(widthMeasureSpec, heightMeasureSpec);
2852 
2853                 mStartWrapWidth = mLayoutStart.getWidth();
2854                 mStartWrapHeight = mLayoutStart.getHeight();
2855                 mEndWrapWidth = mLayoutEnd.getWidth();
2856                 mEndWrapHeight = mLayoutEnd.getHeight();
2857                 mMeasureDuringTransition = ((mStartWrapWidth != mEndWrapWidth)
2858                         || (mStartWrapHeight != mEndWrapHeight));
2859             }
2860 
2861             int width = mStartWrapWidth;
2862             int height = mStartWrapHeight;
2863             if (mWidthMeasureMode == MeasureSpec.AT_MOST
2864                     || mWidthMeasureMode == MeasureSpec.UNSPECIFIED) {
2865                 width = (int) (mStartWrapWidth + mPostInterpolationPosition
2866                         * (mEndWrapWidth - mStartWrapWidth));
2867             }
2868             if (mHeightMeasureMode == MeasureSpec.AT_MOST
2869                     || mHeightMeasureMode == MeasureSpec.UNSPECIFIED) {
2870                 height = (int) (mStartWrapHeight + mPostInterpolationPosition
2871                         * (mEndWrapHeight - mStartWrapHeight));
2872             }
2873 
2874             boolean isWidthMeasuredTooSmall = mLayoutStart.isWidthMeasuredTooSmall()
2875                     || mLayoutEnd.isWidthMeasuredTooSmall();
2876             boolean isHeightMeasuredTooSmall = mLayoutStart.isHeightMeasuredTooSmall()
2877                     || mLayoutEnd.isHeightMeasuredTooSmall();
2878             resolveMeasuredDimension(widthMeasureSpec, heightMeasureSpec,
2879                     width, height, isWidthMeasuredTooSmall, isHeightMeasuredTooSmall);
2880 
2881             if (DEBUG) {
2882                 Debug.logStack(TAG, ">>>>>>>>", 3);
2883                 debugLayout(">>>>>>> measure str ", mLayoutStart);
2884                 debugLayout(">>>>>>> measure end ", mLayoutEnd);
2885             }
2886         }
2887 
2888         private void computeStartEndSize(int widthMeasureSpec, int heightMeasureSpec) {
2889             int optimisationLevel = getOptimizationLevel();
2890 
2891             if (mCurrentState == getStartState()) {
2892                 resolveSystem(mLayoutEnd, optimisationLevel,
2893                         (mEnd == null || mEnd.mRotate == 0) ? widthMeasureSpec : heightMeasureSpec,
2894                         (mEnd == null || mEnd.mRotate == 0) ? heightMeasureSpec : widthMeasureSpec);
2895                 if (mStart != null) {
2896                     resolveSystem(mLayoutStart, optimisationLevel,
2897                             (mStart.mRotate == 0) ? widthMeasureSpec : heightMeasureSpec,
2898                             (mStart.mRotate == 0) ? heightMeasureSpec : widthMeasureSpec);
2899                 }
2900             } else {
2901                 if (mStart != null) {
2902                     resolveSystem(mLayoutStart, optimisationLevel,
2903                             (mStart.mRotate == 0) ? widthMeasureSpec : heightMeasureSpec,
2904                             (mStart.mRotate == 0) ? heightMeasureSpec : widthMeasureSpec);
2905                 }
2906                 resolveSystem(mLayoutEnd, optimisationLevel,
2907                         (mEnd == null || mEnd.mRotate == 0) ? widthMeasureSpec : heightMeasureSpec,
2908                         (mEnd == null || mEnd.mRotate == 0) ? heightMeasureSpec : widthMeasureSpec);
2909             }
2910         }
2911 
2912         public void build() {
2913             final int n = getChildCount();
2914             mFrameArrayList.clear();
2915             SparseArray<MotionController> controllers = new SparseArray<>();
2916             int[] ids = new int[n];
2917             for (int i = 0; i < n; i++) {
2918                 View v = getChildAt(i);
2919                 MotionController motionController = new MotionController(v);
2920                 controllers.put(ids[i] = v.getId(), motionController);
2921                 mFrameArrayList.put(v, motionController);
2922             }
2923             for (int i = 0; i < n; i++) {
2924                 View v = getChildAt(i);
2925                 MotionController motionController = mFrameArrayList.get(v);
2926                 if (motionController == null) {
2927                     continue;
2928                 }
2929                 if (mStart != null) {
2930                     ConstraintWidget startWidget = getWidget(mLayoutStart, v);
2931                     if (startWidget != null) {
2932                         motionController.setStartState(toRect(startWidget), mStart,
2933                                 getWidth(), getHeight());
2934                     } else {
2935                         if (mDebugPath != 0) {
2936                             Log.e(TAG, Debug.getLocation() + "no widget for  "
2937                                     + Debug.getName(v) + " (" + v.getClass().getName() + ")");
2938                         }
2939                     }
2940                 } else {
2941                     if (mInRotation) {
2942                         motionController.setStartState(mPreRotate.get(v), v, mRotatMode,
2943                                 mPreRotateWidth, mPreRotateHeight);
2944                     }
2945                 }
2946                 if (mEnd != null) {
2947                     ConstraintWidget endWidget = getWidget(mLayoutEnd, v);
2948                     if (endWidget != null) {
2949                         motionController.setEndState(toRect(endWidget), mEnd,
2950                                 getWidth(), getHeight());
2951                     } else {
2952                         if (mDebugPath != 0) {
2953                             Log.e(TAG, Debug.getLocation() + "no widget for  "
2954                                     + Debug.getName(v)
2955                                     + " (" + v.getClass().getName() + ")");
2956                         }
2957                     }
2958                 }
2959             }
2960 
2961             for (int i = 0; i < n; i++) {
2962                 MotionController controller = controllers.get(ids[i]);
2963                 int relativeToId = controller.getAnimateRelativeTo();
2964                 if (relativeToId != UNSET) {
2965                     controller.setupRelative(controllers.get(relativeToId));
2966                 }
2967             }
2968         }
2969 
2970         public void setMeasuredId(int startId, int endId) {
2971             mStartId = startId;
2972             mEndId = endId;
2973         }
2974 
2975         public boolean isNotConfiguredWith(int startId, int endId) {
2976             return startId != mStartId || endId != mEndId;
2977         }
2978     }
2979 
2980     Model mModel = new Model();
2981 
2982     private Rect toRect(ConstraintWidget cw) {
2983         mTempRect.top = cw.getY();
2984         mTempRect.left = cw.getX();
2985         mTempRect.right = cw.getWidth() + mTempRect.left;
2986         mTempRect.bottom = cw.getHeight() + mTempRect.top;
2987         return mTempRect;
2988     }
2989 
2990     @Override
2991     public void requestLayout() {
2992         if (!mMeasureDuringTransition) {
2993             if (mCurrentState == UNSET && mScene != null
2994                     && mScene.mCurrentTransition != null) {
2995                 int mode = mScene.mCurrentTransition.getLayoutDuringTransition();
2996                 if (mode == MotionScene.LAYOUT_IGNORE_REQUEST) {
2997                     return;
2998                 } else if (mode == MotionScene.LAYOUT_CALL_MEASURE) {
2999                     final int n = getChildCount();
3000                     for (int i = 0; i < n; i++) {
3001                         View v = getChildAt(i);
3002                         MotionController motionController = mFrameArrayList.get(v);
3003                         motionController.remeasure();
3004                     }
3005                     return;
3006                 }
3007             }
3008         }
3009         super.requestLayout();
3010     }
3011 
3012     @Override
3013     public String toString() {
3014         Context context = getContext();
3015         return Debug.getName(context, mBeginState) + "->"
3016                 + Debug.getName(context, mEndState)
3017                 + " (pos:" + mTransitionLastPosition + " Dpos/Dt:" + mLastVelocity;
3018     }
3019 
3020     @Override
3021     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3022         if (DEBUG) {
3023             Log.v(TAG, "onMeasure " + Debug.getLocation());
3024         }
3025         if (mScene == null) {
3026             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
3027             return;
3028         }
3029         boolean recalc = (mLastWidthMeasureSpec != widthMeasureSpec
3030                 || mLastHeightMeasureSpec != heightMeasureSpec);
3031         if (mNeedsFireTransitionCompleted) {
3032             mNeedsFireTransitionCompleted = false;
3033             onNewStateAttachHandlers();
3034             processTransitionCompleted();
3035             recalc = true;
3036         }
3037 
3038         if (mDirtyHierarchy) {
3039             recalc = true;
3040         }
3041 
3042         mLastWidthMeasureSpec = widthMeasureSpec;
3043         mLastHeightMeasureSpec = heightMeasureSpec;
3044 
3045         int startId = mScene.getStartId();
3046         int endId = mScene.getEndId();
3047         boolean setMeasure = true;
3048         if ((recalc || mModel.isNotConfiguredWith(startId, endId)) && mBeginState != UNSET) {
3049             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
3050             mModel.initFrom(mLayoutWidget, mScene.getConstraintSet(startId),
3051                     mScene.getConstraintSet(endId));
3052             mModel.reEvaluateState();
3053             mModel.setMeasuredId(startId, endId);
3054             setMeasure = false;
3055         } else if (recalc) {
3056             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
3057         }
3058 
3059         if (mMeasureDuringTransition || setMeasure) {
3060             int heightPadding = getPaddingTop() + getPaddingBottom();
3061             int widthPadding = getPaddingLeft() + getPaddingRight();
3062             int androidLayoutWidth = mLayoutWidget.getWidth() + widthPadding;
3063             int androidLayoutHeight = mLayoutWidget.getHeight() + heightPadding;
3064             if (mWidthMeasureMode == MeasureSpec.AT_MOST
3065                     || mWidthMeasureMode == MeasureSpec.UNSPECIFIED) {
3066                 androidLayoutWidth = (int) (mStartWrapWidth + mPostInterpolationPosition
3067                         * (mEndWrapWidth - mStartWrapWidth));
3068                 requestLayout();
3069             }
3070             if (mHeightMeasureMode == MeasureSpec.AT_MOST
3071                     || mHeightMeasureMode == MeasureSpec.UNSPECIFIED) {
3072                 androidLayoutHeight = (int) (mStartWrapHeight + mPostInterpolationPosition
3073                         * (mEndWrapHeight - mStartWrapHeight));
3074                 requestLayout();
3075             }
3076             setMeasuredDimension(androidLayoutWidth, androidLayoutHeight);
3077         }
3078         evaluateLayout();
3079     }
3080 
3081     @Override
3082     public boolean onStartNestedScroll(@NonNull View child,
3083                                        @NonNull View target,
3084                                        int axes, int type) {
3085         if (DEBUG) {
3086             Log.v(TAG, "********** onStartNestedScroll( child:" + Debug.getName(child)
3087                     + ", target:" + Debug.getName(target) + ", axis:" + axes + ", type:" + type);
3088         }
3089         if (mScene == null
3090                 || mScene.mCurrentTransition == null
3091                 || mScene.mCurrentTransition.getTouchResponse() == null
3092                 || (mScene.mCurrentTransition.getTouchResponse().getFlags()
3093                 & TouchResponse.FLAG_DISABLE_SCROLL) != 0) {
3094             return false;
3095         }
3096         return true;
3097     }
3098 
3099     @Override
3100     public void onNestedScrollAccepted(@NonNull View child,
3101                                        @NonNull View target,
3102                                        int axes,
3103                                        int type) {
3104         if (DEBUG) {
3105             Log.v(TAG, "********** onNestedScrollAccepted( child:" + Debug.getName(child)
3106                     + ", target:" + Debug.getName(target) + ", axis:" + axes + ", type:" + type);
3107         }
3108         mScrollTargetTime = getNanoTime();
3109         mScrollTargetDT = 0;
3110         mScrollTargetDX = 0;
3111         mScrollTargetDY = 0;
3112     }
3113 
3114     @Override
3115     public void onStopNestedScroll(@NonNull View target, int type) {
3116         if (DEBUG) {
3117             Log.v(TAG, "********** onStopNestedScroll(   target:"
3118                     + Debug.getName(target) + " , type:" + type + " "
3119                     + mScrollTargetDX + ", " + mScrollTargetDY);
3120             Debug.logStack(TAG, "onStopNestedScroll ", 8);
3121 
3122         }
3123         if (mScene == null || mScrollTargetDT == 0) {
3124             return;
3125         }
3126         mScene.processScrollUp(mScrollTargetDX / mScrollTargetDT,
3127                 mScrollTargetDY / mScrollTargetDT);
3128     }
3129 
3130     @Override
3131     public void onNestedScroll(@NonNull View target,
3132                                int dxConsumed,
3133                                int dyConsumed,
3134                                int dxUnconsumed,
3135                                int dyUnconsumed,
3136                                int type, int[] consumed) {
3137         if (mUndergoingMotion || dxConsumed != 0 || dyConsumed != 0) {
3138             consumed[0] += dxUnconsumed;
3139             consumed[1] += dyUnconsumed;
3140         }
3141         mUndergoingMotion = false;
3142     }
3143 
3144     @Override
3145     public void onNestedScroll(@NonNull View target,
3146                                int dxConsumed,
3147                                int dyConsumed,
3148                                int dxUnconsumed,
3149                                int dyUnconsumed,
3150                                int type) {
3151         if (DEBUG) {
3152             Log.v(TAG, "********** onNestedScroll( target:" + Debug.getName(target)
3153                     + ", dxConsumed:" + dxConsumed
3154                     + ", dyConsumed:" + dyConsumed
3155                     + ", dyConsumed:" + dxUnconsumed
3156                     + ", dyConsumed:" + dyUnconsumed + ", type:" + type);
3157         }
3158     }
3159 
3160     @Override
3161     public void onNestedPreScroll(@NonNull View target,
3162                                   int dx,
3163                                   int dy,
3164                                   int @NonNull [] consumed,
3165                                   int type) {
3166 
3167         MotionScene scene = mScene;
3168         if (scene == null) {
3169             return;
3170         }
3171 
3172         MotionScene.Transition currentTransition = scene.mCurrentTransition;
3173         if (currentTransition == null || !currentTransition.isEnabled()) {
3174             return;
3175         }
3176 
3177         if (currentTransition.isEnabled()) {
3178             TouchResponse touchResponse = currentTransition.getTouchResponse();
3179             if (touchResponse != null) {
3180                 int regionId = touchResponse.getTouchRegionId();
3181                 if (regionId != MotionScene.UNSET && target.getId() != regionId) {
3182                     return;
3183                 }
3184             }
3185         }
3186 
3187         if (scene.getMoveWhenScrollAtTop()) {
3188             // This blocks transition during scrolling
3189             TouchResponse touchResponse = currentTransition.getTouchResponse();
3190             int vert = -1;
3191             if (touchResponse != null) {
3192                 if ((touchResponse.getFlags() & TouchResponse.FLAG_SUPPORT_SCROLL_UP) != 0) {
3193                     vert = dy;
3194                 }
3195             }
3196             if ((mTransitionPosition == 1 || mTransitionPosition == 0)
3197                     && target.canScrollVertically(vert)) {
3198                 return;
3199             }
3200         }
3201 
3202         // This should be disabled in androidx
3203         if (currentTransition.getTouchResponse() != null
3204                 && (currentTransition.getTouchResponse().getFlags()
3205                 & TouchResponse.FLAG_DISABLE_POST_SCROLL) != 0) {
3206             float dir = scene.getProgressDirection(dx, dy);
3207             if ((mTransitionLastPosition <= 0.0f && (dir < 0))
3208                     || (mTransitionLastPosition >= 1.0f && (dir > 0))) {
3209                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
3210                     target.setNestedScrollingEnabled(false);
3211                     // TODO find a better hack
3212                     target.post(new Runnable() {
3213                         @Override
3214                         public void run() {
3215                             target.setNestedScrollingEnabled(true);
3216                         }
3217                     });
3218                 }
3219                 return;
3220             }
3221         }
3222 
3223         if (DEBUG) {
3224             Log.v(TAG, "********** onNestedPreScroll(target:"
3225                     + Debug.getName(target) + ", dx:" + dx + ", dy:" + dy + ", type:" + type);
3226         }
3227         float progress = mTransitionPosition;
3228         long time = getNanoTime();
3229         mScrollTargetDX = dx;
3230         mScrollTargetDY = dy;
3231         mScrollTargetDT = (float) ((time - mScrollTargetTime) * 1E-9);
3232         mScrollTargetTime = time;
3233         if (DEBUG) {
3234             Log.v(TAG, "********** dy = " + dx + " dy = " + dy + " dt = " + mScrollTargetDT);
3235         }
3236         scene.processScrollMove(dx, dy);
3237         if (progress != mTransitionPosition) {
3238             consumed[0] = dx;
3239             consumed[1] = dy;
3240         }
3241         evaluate(false);
3242         if (consumed[0] != 0 || consumed[1] != 0) {
3243             mUndergoingMotion = true;
3244         }
3245 
3246     }
3247 
3248     @Override
3249     public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
3250         return false;
3251     }
3252 
3253     @Override
3254     public boolean onNestedFling(@NonNull View target,
3255                                  float velocityX,
3256                                  float velocityY,
3257                                  boolean consumed) {
3258         return false;
3259     }
3260 
3261     ////////////////////////////////////////////////////////////////////////////////////////
3262     // Used to draw debugging lines
3263     ////////////////////////////////////////////////////////////////////////////////////////
3264     private class DevModeDraw {
3265         private static final int DEBUG_PATH_TICKS_PER_MS = 16;
3266         float[] mPoints;
3267         int[] mPathMode;
3268         float[] mKeyFramePoints;
3269         Path mPath;
3270         Paint mPaint;
3271         Paint mPaintKeyframes;
3272         Paint mPaintGraph;
3273         Paint mTextPaint;
3274         Paint mFillPaint;
3275         private float[] mRectangle;
3276         final int mRedColor = 0xFFFFAA33;
3277         final int mKeyframeColor = 0xffe0759a;
3278         final int mGraphColor = 0xFF33AA00;
3279         final int mShadowColor = 0x77000000;
3280         final int mDiamondSize = 10;
3281         DashPathEffect mDashPathEffect;
3282         int mKeyFrameCount;
3283         Rect mBounds = new Rect();
3284         boolean mPresentationMode = false;
3285         int mShadowTranslate = 1;
3286 
3287         DevModeDraw() {
3288             mPaint = new Paint();
3289             mPaint.setAntiAlias(true);
3290             mPaint.setColor(mRedColor);
3291             mPaint.setStrokeWidth(2);
3292             mPaint.setStyle(Paint.Style.STROKE);
3293 
3294             mPaintKeyframes = new Paint();
3295             mPaintKeyframes.setAntiAlias(true);
3296             mPaintKeyframes.setColor(mKeyframeColor);
3297             mPaintKeyframes.setStrokeWidth(2);
3298             mPaintKeyframes.setStyle(Paint.Style.STROKE);
3299 
3300             mPaintGraph = new Paint();
3301             mPaintGraph.setAntiAlias(true);
3302             mPaintGraph.setColor(mGraphColor);
3303             mPaintGraph.setStrokeWidth(2);
3304             mPaintGraph.setStyle(Paint.Style.STROKE);
3305 
3306             mTextPaint = new Paint();
3307             mTextPaint.setAntiAlias(true);
3308             mTextPaint.setColor(mGraphColor);
3309             mTextPaint.setTextSize(12 * getContext().getResources().getDisplayMetrics().density);
3310             mRectangle = new float[8];
3311             mFillPaint = new Paint();
3312             mFillPaint.setAntiAlias(true);
3313             mDashPathEffect = new DashPathEffect(new float[]{4, 8}, 0);
3314             mPaintGraph.setPathEffect(mDashPathEffect);
3315             mKeyFramePoints = new float[MAX_KEY_FRAMES * 2];
3316             mPathMode = new int[MAX_KEY_FRAMES];
3317 
3318             if (mPresentationMode) {
3319                 mPaint.setStrokeWidth(8);
3320                 mFillPaint.setStrokeWidth(8);
3321                 mPaintKeyframes.setStrokeWidth(8);
3322                 mShadowTranslate = 4;
3323             }
3324         }
3325 
3326         public void draw(Canvas canvas,
3327                          HashMap<View, MotionController> frameArrayList,
3328                          int duration, int debugPath) {
3329             if (frameArrayList == null || frameArrayList.size() == 0) {
3330                 return;
3331             }
3332             canvas.save();
3333             if (!isInEditMode() && (DEBUG_SHOW_PROGRESS & debugPath) == DEBUG_SHOW_PATH) {
3334                 String str = getContext().getResources().getResourceName(mEndState)
3335                         + ":" + getProgress();
3336                 canvas.drawText(str, 10, getHeight() - 30, mTextPaint);
3337                 canvas.drawText(str, 11, getHeight() - 29, mPaint);
3338             }
3339             for (MotionController motionController : frameArrayList.values()) {
3340                 int mode = motionController.getDrawPath();
3341                 if (debugPath > 0 && mode == MotionController.DRAW_PATH_NONE) {
3342                     mode = MotionController.DRAW_PATH_BASIC;
3343                 }
3344                 if (mode == MotionController.DRAW_PATH_NONE) { // do not draw path
3345                     continue;
3346                 }
3347 
3348                 mKeyFrameCount = motionController.buildKeyFrames(mKeyFramePoints, mPathMode);
3349 
3350                 if (mode >= MotionController.DRAW_PATH_BASIC) {
3351 
3352                     int frames = duration / DEBUG_PATH_TICKS_PER_MS;
3353                     if (mPoints == null || mPoints.length != frames * 2) {
3354                         mPoints = new float[frames * 2];
3355                         mPath = new Path();
3356                     }
3357 
3358                     canvas.translate(mShadowTranslate, mShadowTranslate);
3359 
3360                     mPaint.setColor(mShadowColor);
3361                     mFillPaint.setColor(mShadowColor);
3362                     mPaintKeyframes.setColor(mShadowColor);
3363                     mPaintGraph.setColor(mShadowColor);
3364                     motionController.buildPath(mPoints, frames);
3365                     drawAll(canvas, mode, mKeyFrameCount, motionController);
3366                     mPaint.setColor(mRedColor);
3367                     mPaintKeyframes.setColor(mKeyframeColor);
3368                     mFillPaint.setColor(mKeyframeColor);
3369                     mPaintGraph.setColor(mGraphColor);
3370 
3371                     canvas.translate(-mShadowTranslate, -mShadowTranslate);
3372                     drawAll(canvas, mode, mKeyFrameCount, motionController);
3373                     if (mode == MotionController.DRAW_PATH_RECTANGLE) {
3374                         drawRectangle(canvas, motionController);
3375                     }
3376                 }
3377 
3378             }
3379             canvas.restore();
3380         }
3381 
3382         public void drawAll(Canvas canvas,
3383                             int mode,
3384                             int keyFrames,
3385                             MotionController motionController) {
3386             if (mode == MotionController.DRAW_PATH_AS_CONFIGURED) {
3387                 drawPathAsConfigured(canvas);
3388             }
3389             if (mode == MotionController.DRAW_PATH_RELATIVE) {
3390                 drawPathRelative(canvas);
3391             }
3392             if (mode == MotionController.DRAW_PATH_CARTESIAN) {
3393                 drawPathCartesian(canvas);
3394             }
3395             drawBasicPath(canvas);
3396             drawTicks(canvas, mode, keyFrames, motionController);
3397         }
3398 
3399         private void drawBasicPath(Canvas canvas) {
3400             canvas.drawLines(mPoints, mPaint);
3401         }
3402 
3403         private void drawTicks(Canvas canvas,
3404                                int mode,
3405                                int keyFrames,
3406                                MotionController motionController) {
3407             int viewWidth = 0;
3408             int viewHeight = 0;
3409             if (motionController.mView != null) {
3410                 viewWidth = motionController.mView.getWidth();
3411                 viewHeight = motionController.mView.getHeight();
3412             }
3413             for (int i = 1; i < keyFrames - 1; i++) {
3414                 if (mode == MotionController.DRAW_PATH_AS_CONFIGURED
3415                         && mPathMode[i - 1] == MotionController.DRAW_PATH_NONE) {
3416                     continue;
3417 
3418                 }
3419                 float x = mKeyFramePoints[i * 2];
3420                 float y = mKeyFramePoints[i * 2 + 1];
3421                 mPath.reset();
3422                 mPath.moveTo(x, y + mDiamondSize);
3423                 mPath.lineTo(x + mDiamondSize, y);
3424                 mPath.lineTo(x, y - mDiamondSize);
3425                 mPath.lineTo(x - mDiamondSize, y);
3426                 mPath.close();
3427 
3428                 @SuppressWarnings("unused")
3429                 MotionPaths framePoint = motionController.getKeyFrame(i - 1);
3430                 float dx = 0; //framePoint.translationX;
3431                 float dy = 0; //framePoint.translationY;
3432                 if (mode == MotionController.DRAW_PATH_AS_CONFIGURED) {
3433 
3434                     if (mPathMode[i - 1] == MotionPaths.PERPENDICULAR) {
3435                         drawPathRelativeTicks(canvas, x - dx, y - dy);
3436                     } else if (mPathMode[i - 1] == MotionPaths.CARTESIAN) {
3437                         drawPathCartesianTicks(canvas, x - dx, y - dy);
3438                     } else if (mPathMode[i - 1] == MotionPaths.SCREEN) {
3439                         drawPathScreenTicks(canvas, x - dx, y - dy, viewWidth, viewHeight);
3440                     }
3441 
3442                     canvas.drawPath(mPath, mFillPaint);
3443                 }
3444                 if (mode == MotionController.DRAW_PATH_RELATIVE) {
3445                     drawPathRelativeTicks(canvas, x - dx, y - dy);
3446                 }
3447                 if (mode == MotionController.DRAW_PATH_CARTESIAN) {
3448                     drawPathCartesianTicks(canvas, x - dx, y - dy);
3449                 }
3450                 if (mode == MotionController.DRAW_PATH_SCREEN) {
3451                     drawPathScreenTicks(canvas, x - dx, y - dy, viewWidth, viewHeight);
3452                 }
3453                 if (dx != 0 || dy != 0) {
3454                     drawTranslation(canvas, x - dx, y - dy, x, y);
3455                 } else {
3456                     canvas.drawPath(mPath, mFillPaint);
3457                 }
3458             }
3459             if (mPoints.length > 1) {
3460                 // Draw the starting and ending circle
3461                 canvas.drawCircle(mPoints[0], mPoints[1], 8, mPaintKeyframes);
3462                 canvas.drawCircle(mPoints[mPoints.length - 2],
3463                         mPoints[mPoints.length - 1], 8, mPaintKeyframes);
3464             }
3465         }
3466 
3467         private void drawTranslation(Canvas canvas, float x1, float y1, float x2, float y2) {
3468             canvas.drawRect(x1, y1, x2, y2, mPaintGraph);
3469             canvas.drawLine(x1, y1, x2, y2, mPaintGraph);
3470         }
3471 
3472         private void drawPathRelative(Canvas canvas) {
3473             canvas.drawLine(mPoints[0], mPoints[1],
3474                     mPoints[mPoints.length - 2], mPoints[mPoints.length - 1], mPaintGraph);
3475         }
3476 
3477         private void drawPathAsConfigured(Canvas canvas) {
3478             boolean path = false;
3479             boolean cart = false;
3480             for (int i = 0; i < mKeyFrameCount; i++) {
3481                 if (mPathMode[i] == MotionPaths.PERPENDICULAR) {
3482                     path = true;
3483                 }
3484                 if (mPathMode[i] == MotionPaths.CARTESIAN) {
3485                     cart = true;
3486                 }
3487             }
3488             if (path) {
3489                 drawPathRelative(canvas);
3490             }
3491             if (cart) {
3492                 drawPathCartesian(canvas);
3493             }
3494         }
3495 
3496         private void drawPathRelativeTicks(Canvas canvas, float x, float y) {
3497             float x1 = mPoints[0];
3498             float y1 = mPoints[1];
3499             float x2 = mPoints[mPoints.length - 2];
3500             float y2 = mPoints[mPoints.length - 1];
3501             float dist = (float) Math.hypot(x1 - x2, y1 - y2);
3502             float t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / (dist * dist);
3503             float xp = x1 + t * (x2 - x1);
3504             float yp = y1 + t * (y2 - y1);
3505 
3506             Path path = new Path();
3507             path.moveTo(x, y);
3508             path.lineTo(xp, yp);
3509             float len = (float) Math.hypot(xp - x, yp - y);
3510             String text = "" + ((int) (100 * len / dist)) / 100.0f;
3511             getTextBounds(text, mTextPaint);
3512             float off = len / 2 - mBounds.width() / 2;
3513             canvas.drawTextOnPath(text, path, off, -20, mTextPaint);
3514             canvas.drawLine(x, y, xp, yp, mPaintGraph);
3515         }
3516 
3517         void getTextBounds(String text, Paint paint) {
3518             paint.getTextBounds(text, 0, text.length(), mBounds);
3519         }
3520 
3521         private void drawPathCartesian(Canvas canvas) {
3522             float x1 = mPoints[0];
3523             float y1 = mPoints[1];
3524             float x2 = mPoints[mPoints.length - 2];
3525             float y2 = mPoints[mPoints.length - 1];
3526 
3527             canvas.drawLine(Math.min(x1, x2), Math.max(y1, y2),
3528                     Math.max(x1, x2), Math.max(y1, y2), mPaintGraph);
3529             canvas.drawLine(Math.min(x1, x2), Math.min(y1, y2),
3530                     Math.min(x1, x2), Math.max(y1, y2), mPaintGraph);
3531         }
3532 
3533         private void drawPathCartesianTicks(Canvas canvas, float x, float y) {
3534             float x1 = mPoints[0];
3535             float y1 = mPoints[1];
3536             float x2 = mPoints[mPoints.length - 2];
3537             float y2 = mPoints[mPoints.length - 1];
3538             float minx = Math.min(x1, x2);
3539             float maxy = Math.max(y1, y2);
3540             float xgap = x - Math.min(x1, x2);
3541             float ygap = Math.max(y1, y2) - y;
3542             // Horizontal line
3543             String text = "" + ((int) (0.5 + 100 * xgap / Math.abs(x2 - x1))) / 100.0f;
3544             getTextBounds(text, mTextPaint);
3545             float off = xgap / 2 - mBounds.width() / 2;
3546             canvas.drawText(text, off + minx, y - 20, mTextPaint);
3547             canvas.drawLine(x, y,
3548                     Math.min(x1, x2), y, mPaintGraph);
3549 
3550             // Vertical line
3551             text = "" + ((int) (0.5 + 100 * ygap / Math.abs(y2 - y1))) / 100.0f;
3552             getTextBounds(text, mTextPaint);
3553             off = ygap / 2 - mBounds.height() / 2;
3554             canvas.drawText(text, x + 5, maxy - off, mTextPaint);
3555             canvas.drawLine(x, y,
3556                     x, Math.max(y1, y2), mPaintGraph);
3557         }
3558 
3559         private void drawPathScreenTicks(Canvas canvas,
3560                                          float x,
3561                                          float y,
3562                                          int viewWidth,
3563                                          int viewHeight) {
3564             float x1 = 0;
3565             float y1 = 0;
3566             float x2 = 1;
3567             float y2 = 1;
3568             float minx = 0;
3569             float maxy = 0;
3570             float xgap = x;
3571             float ygap = y;
3572             // Horizontal line
3573             String text = "" + ((int) (0.5 + 100 * (xgap - viewWidth / 2)
3574                     / (getWidth() - viewWidth))) / 100.0f;
3575             getTextBounds(text, mTextPaint);
3576             float off = xgap / 2 - mBounds.width() / 2;
3577             canvas.drawText(text, off + minx, y - 20, mTextPaint);
3578             canvas.drawLine(x, y,
3579                     Math.min(x1, x2), y, mPaintGraph);
3580 
3581             // Vertical line
3582             text = "" + ((int) (0.5 + 100 * (ygap - viewHeight / 2)
3583                     / (getHeight() - viewHeight))) / 100.0f;
3584             getTextBounds(text, mTextPaint);
3585             off = ygap / 2 - mBounds.height() / 2;
3586             canvas.drawText(text, x + 5, maxy - off, mTextPaint);
3587             canvas.drawLine(x, y,
3588                     x, Math.max(y1, y2), mPaintGraph);
3589         }
3590 
3591         private void drawRectangle(Canvas canvas, MotionController motionController) {
3592             mPath.reset();
3593             int rectFrames = 50;
3594             for (int i = 0; i <= rectFrames; i++) {
3595                 float p = i / (float) rectFrames;
3596                 motionController.buildRect(p, mRectangle, 0);
3597                 mPath.moveTo(mRectangle[0], mRectangle[1]);
3598                 mPath.lineTo(mRectangle[2], mRectangle[3]);
3599                 mPath.lineTo(mRectangle[4], mRectangle[5]);
3600                 mPath.lineTo(mRectangle[6], mRectangle[7]);
3601                 mPath.close();
3602             }
3603             mPaint.setColor(0x44000000);
3604             canvas.translate(2, 2);
3605             canvas.drawPath(mPath, mPaint);
3606 
3607             canvas.translate(-2, -2);
3608             mPaint.setColor(0xFFFF0000);
3609             canvas.drawPath(mPath, mPaint);
3610         }
3611 
3612     }
3613 
3614     @SuppressLint("LogConditional")
3615     private void debugPos() {
3616         for (int i = 0; i < getChildCount(); i++) {
3617             final View child = getChildAt(i);
3618             Log.v(TAG, " " + Debug.getLocation() + " " + Debug.getName(this)
3619                     + " " + Debug.getName(getContext(), mCurrentState) + " " + Debug.getName(child)
3620                     + child.getLeft() + " "
3621                     + child.getTop());
3622         }
3623     }
3624 
3625     /**
3626      * Used to draw debugging graphics and to do post layout changes
3627      *
3628      * @param canvas
3629      */
3630     @Override
3631     protected void dispatchDraw(Canvas canvas) {
3632         if (DEBUG) {
3633             Log.v(TAG, " dispatchDraw " + getProgress() + Debug.getLocation());
3634         }
3635         if (mDecoratorsHelpers != null) {
3636             for (MotionHelper decor : mDecoratorsHelpers) {
3637                 decor.onPreDraw(canvas);
3638             }
3639         }
3640         evaluate(false);
3641         if (mScene != null && mScene.mViewTransitionController != null) {
3642             mScene.mViewTransitionController.animate();
3643         }
3644         if (DEBUG) {
3645             Log.v(TAG, " dispatchDraw" + Debug.getLocation() + " " + Debug.getName(this)
3646                     + " " + Debug.getName(getContext(), mCurrentState));
3647             debugPos();
3648         }
3649         super.dispatchDraw(canvas);
3650         if (mScene == null) {
3651             return;
3652         }
3653         if (DEBUG) {
3654             mDebugPath = 0xFF;
3655         }
3656         if ((mDebugPath & 1) == 1) {
3657             if (!isInEditMode()) {
3658                 mFrames++;
3659                 long currentDrawTime = getNanoTime();
3660                 if (mLastDrawTime != -1) {
3661                     long delay = currentDrawTime - mLastDrawTime;
3662                     if (delay > 200000000) {
3663                         float fps = mFrames / (delay * 1E-9f);
3664                         mLastFps = ((int) (fps * 100)) / 100.0f;
3665                         mFrames = 0;
3666                         mLastDrawTime = currentDrawTime;
3667                     }
3668                 } else {
3669                     mLastDrawTime = currentDrawTime;
3670                 }
3671                 Paint paint = new Paint();
3672                 paint.setTextSize(42);
3673                 float p = ((int) (getProgress() * 1000)) / 10f;
3674                 String str = mLastFps + " fps " + Debug.getState(this, mBeginState) + " -> ";
3675                 str += Debug.getState(this, mEndState) + " (progress: " + p + " ) state="
3676                         + ((mCurrentState == UNSET) ? "undefined"
3677                                 : Debug.getState(this, mCurrentState));
3678                 paint.setColor(0xFF000000);
3679                 canvas.drawText(str, 11, getHeight() - 29, paint);
3680                 paint.setColor(0xFF880088);
3681                 canvas.drawText(str, 10, getHeight() - 30, paint);
3682 
3683             }
3684         }
3685         if (mDebugPath > 1) {
3686             if (mDevModeDraw == null) {
3687                 mDevModeDraw = new DevModeDraw();
3688             }
3689             mDevModeDraw.draw(canvas, mFrameArrayList, mScene.getDuration(), mDebugPath);
3690         }
3691         if (mDecoratorsHelpers != null) {
3692             for (MotionHelper decor : mDecoratorsHelpers) {
3693                 decor.onPostDraw(canvas);
3694             }
3695         }
3696     }
3697 
3698     /**
3699      * Direct layout evaluation
3700      */
3701     private void evaluateLayout() {
3702         float dir = Math.signum(mTransitionGoalPosition - mTransitionLastPosition);
3703         long currentTime = getNanoTime();
3704 
3705         float deltaPos = 0f;
3706         if (!(mInterpolator instanceof StopLogic)) { // if we are not in a drag
3707             deltaPos = dir * (currentTime - mTransitionLastTime) * 1E-9f / mTransitionDuration;
3708         }
3709         float position = mTransitionLastPosition + deltaPos;
3710 
3711         boolean done = false;
3712         if (mTransitionInstantly) {
3713             position = mTransitionGoalPosition;
3714         }
3715 
3716         if ((dir > 0 && position >= mTransitionGoalPosition)
3717                 || (dir <= 0 && position <= mTransitionGoalPosition)) {
3718             position = mTransitionGoalPosition;
3719             done = true;
3720         }
3721         if (mInterpolator != null && !done) {
3722             if (mTemporalInterpolator) {
3723                 float time = (currentTime - mAnimationStartTime) * 1E-9f;
3724                 position = mInterpolator.getInterpolation(time);
3725             } else {
3726                 position = mInterpolator.getInterpolation(position);
3727             }
3728         }
3729         if ((dir > 0 && position >= mTransitionGoalPosition)
3730                 || (dir <= 0 && position <= mTransitionGoalPosition)) {
3731             position = mTransitionGoalPosition;
3732         }
3733         mPostInterpolationPosition = position;
3734         int n = getChildCount();
3735         long time = getNanoTime();
3736         float interPos = mProgressInterpolator == null ? position
3737                 : mProgressInterpolator.getInterpolation(position);
3738         for (int i = 0; i < n; i++) {
3739             final View child = getChildAt(i);
3740             final MotionController frame = mFrameArrayList.get(child);
3741             if (frame != null) {
3742                 frame.interpolate(child, interPos, time, mKeyCache);
3743             }
3744         }
3745         if (mMeasureDuringTransition) {
3746             requestLayout();
3747         }
3748     }
3749 
3750     void endTrigger(boolean start) {
3751         int n = getChildCount();
3752         for (int i = 0; i < n; i++) {
3753             final View child = getChildAt(i);
3754             final MotionController frame = mFrameArrayList.get(child);
3755             if (frame != null) {
3756                 frame.endTrigger(start);
3757             }
3758         }
3759     }
3760 
3761     void evaluate(boolean force) {
3762 
3763         if (mTransitionLastTime == -1) {
3764             mTransitionLastTime = getNanoTime();
3765         }
3766         if (mTransitionLastPosition > 0.0f && mTransitionLastPosition < 1.0f) {
3767             mCurrentState = UNSET;
3768         }
3769 
3770         boolean newState = false;
3771         if (mKeepAnimating || (mInTransition
3772                 && (force || mTransitionGoalPosition != mTransitionLastPosition))) {
3773             float dir = Math.signum(mTransitionGoalPosition - mTransitionLastPosition);
3774             long currentTime = getNanoTime();
3775 
3776             float deltaPos = 0f;
3777             if (!(mInterpolator instanceof MotionInterpolator)) { // if we are not in a drag
3778                 deltaPos = dir * (currentTime - mTransitionLastTime) * 1E-9f / mTransitionDuration;
3779             }
3780             float position = mTransitionLastPosition + deltaPos;
3781 
3782             boolean done = false;
3783             if (mTransitionInstantly) {
3784                 position = mTransitionGoalPosition;
3785             }
3786 
3787             if ((dir > 0 && position >= mTransitionGoalPosition)
3788                     || (dir <= 0 && position <= mTransitionGoalPosition)) {
3789                 position = mTransitionGoalPosition;
3790                 mInTransition = false;
3791                 done = true;
3792             }
3793             if (DEBUG) {
3794                 Log.v(TAG, Debug.getLocation() + " mTransitionLastPosition = "
3795                         + mTransitionLastPosition + " position = " + position);
3796             }
3797             mTransitionLastPosition = position;
3798             mTransitionPosition = position;
3799             mTransitionLastTime = currentTime;
3800             int notStopLogic = 0;
3801             int stopLogicContinue = 1;
3802             int stopLogicStop = 2;
3803             int stopLogicDone = notStopLogic;
3804             if (mInterpolator != null && !done) {
3805                 if (mTemporalInterpolator) {
3806                     float time = (currentTime - mAnimationStartTime) * 1E-9f;
3807                     position = mInterpolator.getInterpolation(time);
3808                     if (mInterpolator == mStopLogic) {
3809                         boolean dp = mStopLogic.isStopped();
3810                         stopLogicDone = dp ? stopLogicStop : stopLogicContinue;
3811                     }
3812 
3813                     if (DEBUG) {
3814                         Log.v(TAG, Debug.getLocation() + " mTransitionLastPosition = "
3815                                 + mTransitionLastPosition + " position = " + position);
3816                     }
3817                     mTransitionLastPosition = position;
3818 
3819                     mTransitionLastTime = currentTime;
3820                     if (mInterpolator instanceof MotionInterpolator) {
3821                         float lastVelocity = ((MotionInterpolator) mInterpolator).getVelocity();
3822                         mLastVelocity = lastVelocity;
3823                         if (Math.abs(lastVelocity) * mTransitionDuration <= EPSILON
3824                                 && stopLogicDone == stopLogicStop) {
3825                             mInTransition = false;
3826                         }
3827                         if (lastVelocity > 0 && position >= 1.0f) {
3828                             mTransitionLastPosition = position = 1.0f;
3829                             mInTransition = false;
3830                         }
3831                         if (lastVelocity < 0 && position <= 0) {
3832                             mTransitionLastPosition = position = 0.0f;
3833                             mInTransition = false;
3834                         }
3835                     }
3836 
3837                 } else {
3838 
3839                     float p2 = position;
3840                     position = mInterpolator.getInterpolation(position);
3841                     if (mInterpolator instanceof MotionInterpolator) {
3842                         mLastVelocity = ((MotionInterpolator) mInterpolator).getVelocity();
3843                     } else {
3844                         p2 = mInterpolator.getInterpolation(p2 + deltaPos);
3845                         mLastVelocity = dir * (p2 - position) / deltaPos;
3846                     }
3847 
3848                 }
3849             } else {
3850                 mLastVelocity = deltaPos;
3851             }
3852             if (Math.abs(mLastVelocity) > EPSILON) {
3853                 setState(TransitionState.MOVING);
3854             }
3855 
3856             if (stopLogicDone != stopLogicContinue) {
3857                 if ((dir > 0 && position >= mTransitionGoalPosition)
3858                         || (dir <= 0 && position <= mTransitionGoalPosition)) {
3859                     position = mTransitionGoalPosition;
3860                     mInTransition = false;
3861                 }
3862 
3863                 if (position >= 1.0f || position <= 0.0f) {
3864                     mInTransition = false;
3865                     setState(TransitionState.FINISHED);
3866                 }
3867             }
3868 
3869             int n = getChildCount();
3870             mKeepAnimating = false;
3871             long time = getNanoTime();
3872             if (DEBUG) {
3873                 Log.v(TAG, "LAYOUT frame.interpolate at " + position);
3874             }
3875             mPostInterpolationPosition = position;
3876             float interPos = mProgressInterpolator == null ? position
3877                     : mProgressInterpolator.getInterpolation(position);
3878             if (mProgressInterpolator != null) {
3879                 mLastVelocity =
3880                         mProgressInterpolator
3881                                 .getInterpolation(position + dir / mTransitionDuration);
3882                 mLastVelocity -= mProgressInterpolator.getInterpolation(position);
3883             }
3884             for (int i = 0; i < n; i++) {
3885                 final View child = getChildAt(i);
3886                 final MotionController frame = mFrameArrayList.get(child);
3887                 if (frame != null) {
3888                     mKeepAnimating |= frame.interpolate(child, interPos, time, mKeyCache);
3889                 }
3890             }
3891             if (DEBUG) {
3892                 Log.v(TAG, " interpolate " + Debug.getLocation() + " " + Debug.getName(this)
3893                         + " " + Debug.getName(getContext(), mBeginState) + " " + position);
3894             }
3895 
3896             boolean end = ((dir > 0 && position >= mTransitionGoalPosition)
3897                     || (dir <= 0 && position <= mTransitionGoalPosition));
3898             if (!mKeepAnimating && !mInTransition && end) {
3899                 setState(TransitionState.FINISHED);
3900             }
3901             if (mMeasureDuringTransition) {
3902                 requestLayout();
3903             }
3904 
3905             mKeepAnimating |= !end;
3906 
3907             // If we have hit the begin state begin state could be unset
3908             if (position <= 0 && mBeginState != UNSET) {
3909                 if (mCurrentState != mBeginState) {
3910                     newState = true;
3911                     mCurrentState = mBeginState;
3912                     ConstraintSet set = mScene.getConstraintSet(mBeginState);
3913                     set.applyCustomAttributes(this);
3914                     setState(TransitionState.FINISHED);
3915                 }
3916             }
3917 
3918             if (position >= 1.0) {
3919                 if (DEBUG) {
3920                     Log.v(TAG, Debug.getLoc() + " ============= setting  to end "
3921                             + Debug.getName(getContext(), mEndState) + "  " + position);
3922                 }
3923                 if (mCurrentState != mEndState) {
3924                     newState = true;
3925                     mCurrentState = mEndState;
3926                     ConstraintSet set = mScene.getConstraintSet(mEndState);
3927                     set.applyCustomAttributes(this);
3928                     setState(TransitionState.FINISHED);
3929                 }
3930             }
3931 
3932             if (mKeepAnimating || mInTransition) {
3933                 invalidate();
3934             } else {
3935                 if ((dir > 0 && position == 1) || (dir < 0 && position == 0)) {
3936                     setState(TransitionState.FINISHED);
3937                 }
3938             }
3939             if (!mKeepAnimating && !mInTransition && ((dir > 0 && position == 1)
3940                     || (dir < 0 && position == 0))) {
3941                 onNewStateAttachHandlers();
3942             }
3943         }
3944         if (mTransitionLastPosition >= 1.0f) {
3945             if (mCurrentState != mEndState) {
3946                 newState = true;
3947             }
3948             mCurrentState = mEndState;
3949         } else if (mTransitionLastPosition <= 0.0f) {
3950             if (mCurrentState != mBeginState) {
3951                 newState = true;
3952             }
3953             mCurrentState = mBeginState;
3954         }
3955 
3956         mNeedsFireTransitionCompleted |= newState;
3957 
3958         if (newState && !mInLayout) {
3959             requestLayout();
3960         }
3961 
3962         mTransitionPosition = mTransitionLastPosition;
3963     }
3964 
3965     private boolean mNeedsFireTransitionCompleted = false;
3966 
3967     @Override
3968     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
3969         mInLayout = true;
3970         try {
3971             if (DEBUG) {
3972                 Log.v(TAG, " onLayout " + getProgress() + "  " + Debug.getLocation());
3973             }
3974             if (mScene == null) {
3975                 super.onLayout(changed, left, top, right, bottom);
3976                 return;
3977             }
3978             int w = right - left;
3979             int h = bottom - top;
3980             if (mLastLayoutWidth != w || mLastLayoutHeight != h) {
3981                 rebuildScene();
3982                 evaluate(true);
3983                 if (DEBUG) {
3984                     Log.v(TAG, " onLayout  rebuildScene  " + Debug.getLocation());
3985                 }
3986             }
3987 
3988             mLastLayoutWidth = w;
3989             mLastLayoutHeight = h;
3990             mOldWidth = w;
3991             mOldHeight = h;
3992         } finally {
3993             mInLayout = false;
3994         }
3995     }
3996 
3997     /**
3998      * block ConstraintLayout from handling layout description
3999      *
4000      * @param id
4001      */
4002     @Override
4003     protected void parseLayoutDescription(int id) {
4004         mConstraintLayoutSpec = null;
4005     }
4006 
4007     private void init(AttributeSet attrs) {
4008         IS_IN_EDIT_MODE = isInEditMode();
4009         if (attrs != null) {
4010             TypedArray a = getContext()
4011                     .obtainStyledAttributes(attrs, R.styleable.MotionLayout);
4012             final int count = a.getIndexCount();
4013 
4014             boolean apply = true;
4015             for (int i = 0; i < count; i++) {
4016                 int attr = a.getIndex(i);
4017                 if (attr == R.styleable.MotionLayout_layoutDescription) {
4018                     int n = a.getResourceId(attr, UNSET);
4019                     mScene = new MotionScene(getContext(), this, n);
4020                 } else if (attr == R.styleable.MotionLayout_currentState) {
4021                     mCurrentState = a.getResourceId(attr, UNSET);
4022                 } else if (attr == R.styleable.MotionLayout_motionProgress) {
4023                     mTransitionGoalPosition = a.getFloat(attr, 0.0f);
4024                     mInTransition = true;
4025                 } else if (attr == R.styleable.MotionLayout_applyMotionScene) {
4026                     apply = a.getBoolean(attr, apply);
4027                 } else if (attr == R.styleable.MotionLayout_showPaths) {
4028                     if (mDebugPath == 0) { // favor motionDebug
4029                         mDebugPath = a.getBoolean(attr, false) ? DEBUG_SHOW_PATH : 0;
4030                     }
4031                 } else if (attr == R.styleable.MotionLayout_motionDebug) {
4032                     mDebugPath = a.getInt(attr, 0);
4033                 }
4034             }
4035             a.recycle();
4036             if (mScene == null) {
4037                 Log.e(TAG, "WARNING NO app:layoutDescription tag");
4038             }
4039             if (!apply) {
4040                 mScene = null;
4041             }
4042         }
4043         if (mDebugPath != 0) {
4044             checkStructure();
4045         }
4046         if (mCurrentState == UNSET && mScene != null) {
4047 
4048             mCurrentState = mScene.getStartId();
4049             mBeginState = mScene.getStartId();
4050             if (DEBUG) {
4051                 Log.v(TAG, " ============= init   end is "
4052                         + Debug.getName(getContext(), mEndState));
4053             }
4054             mEndState = mScene.getEndId();
4055             if (DEBUG) {
4056                 Log.v(TAG, " ============= init setting end to "
4057                         + Debug.getName(getContext(), mEndState));
4058             }
4059         }
4060     }
4061 
4062     /**
4063      * Sets a motion scene to the layout. Subsequent calls to it will override the previous scene.
4064      */
4065     public void setScene(MotionScene scene) {
4066         mScene = scene;
4067         mScene.setRtl(isRtl());
4068         rebuildScene();
4069     }
4070 
4071     /**
4072      * Get the motion scene of the layout.
4073      * Warning! This gives you direct access to the internal
4074      * state of the MotionLayout making it easy
4075      * corrupt the state.
4076      * @return the motion scene
4077      */
4078     public MotionScene getScene() {
4079         return mScene;
4080     }
4081 
4082     private void checkStructure() {
4083         if (mScene == null) {
4084             Log.e(TAG, "CHECK: motion scene not set! set \"app:layoutDescription=\"@xml/file\"");
4085             return;
4086         }
4087 
4088         checkStructure(mScene.getStartId(), mScene.getConstraintSet(mScene.getStartId()));
4089         SparseIntArray startToEnd = new SparseIntArray();
4090         SparseIntArray endToStart = new SparseIntArray();
4091         for (MotionScene.Transition definedTransition : mScene.getDefinedTransitions()) {
4092             if (definedTransition == mScene.mCurrentTransition) {
4093                 Log.v(TAG, "CHECK: CURRENT");
4094             }
4095             checkStructure(definedTransition);
4096             int startId = definedTransition.getStartConstraintSetId();
4097             int endId = definedTransition.getEndConstraintSetId();
4098             String startString = Debug.getName(getContext(), startId);
4099             String endString = Debug.getName(getContext(), endId);
4100             if (startToEnd.get(startId) == endId) {
4101 
4102                 Log.e(TAG, "CHECK: two transitions with the same start and end "
4103                         + startString + "->" + endString);
4104             }
4105             if (endToStart.get(endId) == startId) {
4106 
4107                 Log.e(TAG, "CHECK: you can't have reverse transitions"
4108                         + startString + "->" + endString);
4109             }
4110             startToEnd.put(startId, endId);
4111             endToStart.put(endId, startId);
4112             if (mScene.getConstraintSet(startId) == null) {
4113                 Log.e(TAG, " no such constraintSetStart " + startString);
4114             }
4115 
4116             if (mScene.getConstraintSet(endId) == null) {
4117                 Log.e(TAG, " no such constraintSetEnd " + startString);
4118             }
4119         }
4120     }
4121 
4122     private void checkStructure(int csetId, ConstraintSet set) {
4123         String setName = Debug.getName(getContext(), csetId);
4124         int size = getChildCount();
4125         for (int i = 0; i < size; i++) {
4126             View v = getChildAt(i);
4127             int id = v.getId();
4128             if (id == -1) {
4129                 Log.w(TAG, "CHECK: " + setName + " ALL VIEWS SHOULD HAVE ID's "
4130                         + v.getClass().getName() + " does not!");
4131             }
4132             ConstraintSet.Constraint c = set.getConstraint(id);
4133             if (c == null) {
4134                 Log.w(TAG, "CHECK: " + setName + " NO CONSTRAINTS for " + Debug.getName(v));
4135             }
4136         }
4137         int[] ids = set.getKnownIds();
4138         for (int i = 0; i < ids.length; i++) {
4139             int id = ids[i];
4140             String idString = Debug.getName(getContext(), id);
4141             if (null == findViewById(ids[i])) {
4142                 Log.w(TAG, "CHECK: " + setName + " NO View matches id " + idString);
4143             }
4144             if (set.getHeight(id) == UNSET) {
4145                 Log.w(TAG, "CHECK: " + setName + "(" + idString + ") no LAYOUT_HEIGHT");
4146             }
4147             if (set.getWidth(id) == UNSET) {
4148                 Log.w(TAG, "CHECK: " + setName + "(" + idString + ") no LAYOUT_HEIGHT");
4149             }
4150         }
4151     }
4152 
4153     private void checkStructure(MotionScene.Transition transition) {
4154         if (DEBUG) {
4155             Log.v(TAG, "CHECK: transition = " + transition.debugString(getContext()));
4156             Log.v(TAG, "CHECK: transition.setDuration = " + transition.getDuration());
4157         }
4158         if (transition.getStartConstraintSetId() == transition.getEndConstraintSetId()) {
4159             Log.e(TAG, "CHECK: start and end constraint set should not be the same!");
4160         }
4161     }
4162 
4163     /**
4164      * Display the debugging information such as paths information
4165      *
4166      * @param debugMode integer representing various debug modes
4167      *
4168      */
4169     public void setDebugMode(int debugMode) {
4170         mDebugPath = debugMode;
4171         invalidate();
4172     }
4173 
4174     private RectF mBoundsCheck = new RectF();
4175     private View mRegionView = null;
4176     private Matrix mInverseMatrix = null;
4177 
4178     private boolean callTransformedTouchEvent(View view,
4179                                               MotionEvent event,
4180                                               float offsetX,
4181                                               float offsetY) {
4182         Matrix viewMatrix = view.getMatrix();
4183 
4184         if (viewMatrix.isIdentity()) {
4185             event.offsetLocation(offsetX, offsetY);
4186             boolean handled = view.onTouchEvent(event);
4187             event.offsetLocation(-offsetX, -offsetY);
4188 
4189             return handled;
4190         }
4191 
4192         MotionEvent transformedEvent = MotionEvent.obtain(event);
4193 
4194         transformedEvent.offsetLocation(offsetX, offsetY);
4195 
4196         if (mInverseMatrix == null) {
4197             mInverseMatrix = new Matrix();
4198         }
4199 
4200         viewMatrix.invert(mInverseMatrix);
4201         transformedEvent.transform(mInverseMatrix);
4202 
4203         boolean handled = view.onTouchEvent(transformedEvent);
4204 
4205         transformedEvent.recycle();
4206 
4207         return handled;
4208     }
4209 
4210     /**
4211      * Walk the view tree to see if a child view handles a touch event.
4212      *
4213      * @param x
4214      * @param y
4215      * @param view
4216      * @param event
4217      * @return
4218      */
4219     private boolean handlesTouchEvent(float x, float y, View view, MotionEvent event) {
4220         boolean handled = false;
4221         if (view instanceof ViewGroup) {
4222             ViewGroup group = (ViewGroup) view;
4223             final int childCount = group.getChildCount();
4224             for (int i = childCount - 1; i >= 0; i--) {
4225                 View child = group.getChildAt(i);
4226                 if (handlesTouchEvent(x + child.getLeft() - view.getScrollX(),
4227                         y + child.getTop() - view.getScrollY(),
4228                         child, event)) {
4229                     handled = true;
4230                     break;
4231                 }
4232             }
4233         }
4234 
4235         if (!handled) {
4236             mBoundsCheck.set(x, y,
4237                     x + view.getRight() - view.getLeft(),
4238                     y + view.getBottom() - view.getTop());
4239 
4240             if (event.getAction() != MotionEvent.ACTION_DOWN
4241                     || mBoundsCheck.contains(event.getX(), event.getY())) {
4242                 if (callTransformedTouchEvent(view, event, -x, -y)) {
4243                     handled = true;
4244                 }
4245             }
4246         }
4247 
4248         return handled;
4249     }
4250 
4251     /**
4252      * Intercepts the touch event to correctly handle touch region id handover
4253      *
4254      * @param event
4255      * @return
4256      */
4257     @Override
4258     public boolean onInterceptTouchEvent(MotionEvent event) {
4259         if (mScene == null || !mInteractionEnabled) {
4260             return false;
4261         }
4262 
4263         if (mScene.mViewTransitionController != null) {
4264             mScene.mViewTransitionController.touchEvent(event);
4265         }
4266         MotionScene.Transition currentTransition = mScene.mCurrentTransition;
4267         if (currentTransition != null && currentTransition.isEnabled()) {
4268             TouchResponse touchResponse = currentTransition.getTouchResponse();
4269             if (touchResponse != null) {
4270                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
4271                     RectF region = touchResponse.getTouchRegion(this, new RectF());
4272                     if (region != null
4273                             && !region.contains(event.getX(), event.getY())) {
4274                         return false;
4275                     }
4276                 }
4277                 int regionId = touchResponse.getTouchRegionId();
4278                 if (regionId != MotionScene.UNSET) {
4279                     if (mRegionView == null || mRegionView.getId() != regionId) {
4280                         mRegionView = findViewById(regionId);
4281                     }
4282                     if (mRegionView != null) {
4283                         mBoundsCheck.set(mRegionView.getLeft(),
4284                                 mRegionView.getTop(),
4285                                 mRegionView.getRight(),
4286                                 mRegionView.getBottom());
4287                         if (mBoundsCheck.contains(event.getX(), event.getY())) {
4288                             // In case of region id, if the view or a child of the view
4289                             // handles an event we don't need to do anything;
4290                             if (!handlesTouchEvent(mRegionView.getLeft(), mRegionView.getTop(),
4291                                     mRegionView, event)) {
4292                                 // but if not, then *we* need to handle the event.
4293                                 return onTouchEvent(event);
4294                             }
4295                         }
4296                     }
4297                 }
4298             }
4299         }
4300         return false;
4301     }
4302 
4303     @Override
4304     public boolean onTouchEvent(MotionEvent event) {
4305         if (DEBUG) {
4306             Log.v(TAG, Debug.getLocation() + " onTouchEvent = " + mTransitionLastPosition);
4307         }
4308         if (mScene != null && mInteractionEnabled && mScene.supportTouch()) {
4309             MotionScene.Transition currentTransition = mScene.mCurrentTransition;
4310             if (currentTransition != null && !currentTransition.isEnabled()) {
4311                 return super.onTouchEvent(event);
4312             }
4313             mScene.processTouchEvent(event, getCurrentState(), this);
4314             if (mScene.mCurrentTransition.isTransitionFlag(TRANSITION_FLAG_INTERCEPT_TOUCH)) {
4315                 return mScene.mCurrentTransition.getTouchResponse().isDragStarted();
4316             }
4317             return true;
4318         }
4319         if (DEBUG) {
4320             Log.v(TAG, Debug.getLocation() + " mTransitionLastPosition = "
4321                     + mTransitionLastPosition);
4322         }
4323         return super.onTouchEvent(event);
4324     }
4325 
4326     @Override
4327     protected void onAttachedToWindow() {
4328         super.onAttachedToWindow();
4329         Display display = getDisplay();
4330         if (display != null) {
4331             mPreviouseRotation = display.getRotation();
4332         }
4333         if (mScene != null && mCurrentState != UNSET) {
4334             ConstraintSet cSet = mScene.getConstraintSet(mCurrentState);
4335             mScene.readFallback(this);
4336             if (mDecoratorsHelpers != null) {
4337                 for (MotionHelper mh : mDecoratorsHelpers) {
4338                     mh.onFinishedMotionScene(this);
4339                 }
4340             }
4341             if (cSet != null) {
4342                 cSet.applyTo(this);
4343             }
4344             mBeginState = mCurrentState;
4345         }
4346         onNewStateAttachHandlers();
4347         if (mStateCache != null) {
4348             if (mDelayedApply) {
4349                 post(new Runnable() {
4350                     @Override
4351                     public void run() {
4352                         mStateCache.apply();
4353                     }
4354                 });
4355             } else {
4356                 mStateCache.apply();
4357             }
4358         } else {
4359             if (mScene != null && mScene.mCurrentTransition != null) {
4360                 if (mScene.mCurrentTransition.getAutoTransition()
4361                         == MotionScene.Transition.AUTO_ANIMATE_TO_END) {
4362                     transitionToEnd();
4363                     setState(TransitionState.SETUP);
4364                     setState(TransitionState.MOVING);
4365                 }
4366             }
4367         }
4368     }
4369 
4370     @Override
4371     public void onRtlPropertiesChanged(int layoutDirection) {
4372         if (mScene != null) {
4373             mScene.setRtl(isRtl());
4374         }
4375     }
4376 
4377     /**
4378      * This function will set up various handlers (swipe, click...) whenever
4379      * a new state is reached.
4380      */
4381     void onNewStateAttachHandlers() {
4382         if (mScene == null) {
4383             return;
4384         }
4385         if (mScene.autoTransition(this, mCurrentState)) {
4386             requestLayout();
4387             return;
4388         }
4389         if (mCurrentState != UNSET) {
4390             mScene.addOnClickListeners(this, mCurrentState);
4391         }
4392         if (mScene.supportTouch()) {
4393             mScene.setupTouch();
4394         }
4395     }
4396 
4397     /**
4398      * Return the current state id
4399      *
4400      * @return current state id
4401      */
4402     public int getCurrentState() {
4403         return mCurrentState;
4404     }
4405 
4406     /**
4407      * Get current position during an animation.
4408      *
4409      * @return current position from 0.0 to 1.0 inclusive
4410      */
4411     public float getProgress() {
4412         return mTransitionLastPosition;
4413     }
4414 
4415     /**
4416      * Provide an estimate of the motion with respect to change in transitionPosition
4417      * (assume you are currently in a transition)
4418      *
4419      * @param mTouchAnchorId id of the anchor view that will be "moved" by touch
4420      * @param pos            the transition position at which to estimate the position
4421      * @param locationX      the x location within the view (0.0 = left , 1.0 = right)
4422      * @param locationY      the y location within the view (0.0 = left , 1.0 = right)
4423      * @param mAnchorDpDt    returns the dx/dp and dy/dp
4424      */
4425     void getAnchorDpDt(int mTouchAnchorId,
4426                        float pos,
4427                        float locationX, float locationY,
4428                        float[] mAnchorDpDt) {
4429         View v;
4430         MotionController f = mFrameArrayList.get(v = getViewById(mTouchAnchorId));
4431         if (DEBUG) {
4432             Log.v(TAG, " getAnchorDpDt " + Debug.getName(v) + " " + Debug.getLocation());
4433         }
4434         if (f != null) {
4435             f.getDpDt(pos, locationX, locationY, mAnchorDpDt);
4436             float y = v.getY();
4437             float deltaPos = pos - mLastPos;
4438             float deltaY = y - mLastY;
4439             @SuppressWarnings("unused")
4440             float dydp = (deltaPos != 0.0f) ? deltaY / deltaPos : Float.NaN;
4441             if (DEBUG) {
4442                 Log.v(TAG, " getAnchorDpDt " + Debug.getName(v) + " "
4443                         + Debug.getLocation() + " " + Arrays.toString(mAnchorDpDt));
4444             }
4445 
4446             mLastPos = pos;
4447             mLastY = y;
4448         } else {
4449             String idName = (v == null) ? "" + mTouchAnchorId :
4450                     v.getContext().getResources().getResourceName(mTouchAnchorId);
4451             Log.w(TAG, "WARNING could not find view id " + idName);
4452         }
4453     }
4454 
4455     /**
4456      * Gets the time of the currently set animation.
4457      *
4458      * @return time in Milliseconds
4459      */
4460     public long getTransitionTimeMs() {
4461         if (mScene != null) {
4462             mTransitionDuration = mScene.getDuration() / 1000f;
4463         }
4464         return (long) (mTransitionDuration * 1000);
4465     }
4466 
4467     /**
4468      * Set a listener to be notified of drawer events.
4469      *
4470      * @param listener Listener to notify when drawer events occur
4471      * @see TransitionListener
4472      */
4473     public void setTransitionListener(TransitionListener listener) {
4474         mTransitionListener = listener;
4475     }
4476 
4477     /**
4478      * adds a listener to be notified of drawer events.
4479      *
4480      * @param listener Listener to notify when drawer events occur
4481      * @see TransitionListener
4482      */
4483     public void addTransitionListener(TransitionListener listener) {
4484         if (mTransitionListeners == null) {
4485             mTransitionListeners = new CopyOnWriteArrayList<>();
4486         }
4487         mTransitionListeners.add(listener);
4488     }
4489 
4490     /**
4491      * adds a listener to be notified of drawer events.
4492      *
4493      * @param listener Listener to notify when drawer events occur
4494      * @return <tt>true</tt> if it contained the specified listener
4495      * @see TransitionListener
4496      */
4497     public boolean removeTransitionListener(TransitionListener listener) {
4498         if (mTransitionListeners == null) {
4499             return false;
4500         }
4501         return mTransitionListeners.remove(listener);
4502     }
4503 
4504     /**
4505      * Listener for monitoring events about TransitionLayout. <b>Added in 2.0</b>
4506      */
4507     public interface TransitionListener {
4508         /**
4509          * Called when a drawer is about to start a transition.
4510          * Note. startId may be -1 if starting from an "undefined state"
4511          *
4512          * @param motionLayout The TransitionLayout view that was moved
4513          * @param startId      the id of the start state (or ConstraintSet). Will be -1 if unknown.
4514          * @param endId        the id of the end state (or ConstraintSet).
4515          */
4516         void onTransitionStarted(MotionLayout motionLayout,
4517                                         int startId, int endId);
4518 
4519         /**
4520          * Called when a drawer's position changes.
4521          *
4522          * @param motionLayout The TransitionLayout view that was moved
4523          * @param startId      the id of the start state (or ConstraintSet). Will be -1 if unknown.
4524          * @param endId        the id of the end state (or ConstraintSet).
4525          * @param progress     The progress on this transition, from 0 to 1.
4526          */
4527         void onTransitionChange(MotionLayout motionLayout,
4528                                 int startId, int endId,
4529                                 float progress);
4530 
4531         /**
4532          * Called when a drawer has settled completely a state.
4533          * The TransitionLayout is interactive at this point.
4534          *
4535          * @param motionLayout Drawer view that is now open
4536          * @param currentId    the id it has reached
4537          */
4538         void onTransitionCompleted(MotionLayout motionLayout, int currentId);
4539 
4540         /**
4541          * Call when a trigger is fired
4542          *
4543          * @param motionLayout
4544          * @param triggerId    The id set set with triggerID
4545          * @param positive     for positive transition edge
4546          * @param progress
4547          */
4548         void onTransitionTrigger(MotionLayout motionLayout, int triggerId, boolean positive,
4549                                  float progress);
4550     }
4551 
4552     /**
4553      * This causes the callback onTransitionTrigger to be called
4554      *
4555      * @param triggerId The id set set with triggerID
4556      * @param positive  for positive transition edge
4557      * @param progress  the current progress
4558      */
4559     public void fireTrigger(int triggerId, boolean positive, float progress) {
4560         if (mTransitionListener != null) {
4561             mTransitionListener.onTransitionTrigger(this, triggerId, positive, progress);
4562         }
4563         if (mTransitionListeners != null) {
4564             for (TransitionListener listeners : mTransitionListeners) {
4565                 listeners.onTransitionTrigger(this, triggerId, positive, progress);
4566             }
4567         }
4568     }
4569 
4570     private void fireTransitionChange() {
4571         if (mTransitionListener != null
4572                 || (mTransitionListeners != null && !mTransitionListeners.isEmpty())) {
4573             if (mListenerPosition != mTransitionPosition) {
4574                 if (mListenerState != UNSET) {
4575                     fireTransitionStarted();
4576                     mIsAnimating = true;
4577                 }
4578                 mListenerState = UNSET;
4579                 mListenerPosition = mTransitionPosition;
4580                 if (mTransitionListener != null) {
4581                     mTransitionListener.onTransitionChange(this,
4582                             mBeginState, mEndState, mTransitionPosition);
4583                 }
4584                 if (mTransitionListeners != null) {
4585                     for (TransitionListener listeners : mTransitionListeners) {
4586                         listeners.onTransitionChange(this,
4587                                 mBeginState, mEndState, mTransitionPosition);
4588                     }
4589                 }
4590                 mIsAnimating = true;
4591             }
4592         }
4593     }
4594 
4595     ArrayList<Integer> mTransitionCompleted = new ArrayList<>();
4596 
4597     /**
4598      * This causes the callback TransitionCompleted to be called
4599      */
4600     protected void fireTransitionCompleted() {
4601         if (mTransitionListener != null
4602                 || (mTransitionListeners != null && !mTransitionListeners.isEmpty())) {
4603             if (mListenerState == UNSET) {
4604                 mListenerState = mCurrentState;
4605                 int lastState = UNSET;
4606                 if (!mTransitionCompleted.isEmpty()) {
4607                     lastState = mTransitionCompleted.get(mTransitionCompleted.size() - 1);
4608                 }
4609                 if (lastState != mCurrentState && mCurrentState != -1) {
4610                     mTransitionCompleted.add(mCurrentState);
4611                 }
4612             }
4613         }
4614         processTransitionCompleted();
4615         if (mOnComplete != null) {
4616             mOnComplete.run();
4617             mOnComplete = null;
4618         }
4619 
4620         if (mScheduledTransitionTo != null && mScheduledTransitions > 0) {
4621             transitionToState(mScheduledTransitionTo[0]);
4622             System.arraycopy(mScheduledTransitionTo,
4623                     1, mScheduledTransitionTo,
4624                     0, mScheduledTransitionTo.length - 1);
4625             mScheduledTransitions--;
4626         }
4627     }
4628 
4629     private void processTransitionCompleted() {
4630         if (mTransitionListener == null
4631                 && (mTransitionListeners == null || mTransitionListeners.isEmpty())) {
4632             return;
4633         }
4634         mIsAnimating = false;
4635         for (Integer state : mTransitionCompleted) {
4636             if (mTransitionListener != null) {
4637                 mTransitionListener.onTransitionCompleted(this, state);
4638             }
4639             if (mTransitionListeners != null) {
4640                 for (TransitionListener listeners : mTransitionListeners) {
4641                     listeners.onTransitionCompleted(this, state);
4642                 }
4643             }
4644         }
4645         mTransitionCompleted.clear();
4646     }
4647 
4648     /**
4649      *
4650      */
4651     public DesignTool getDesignTool() {
4652         if (mDesignTool == null) {
4653             mDesignTool = new DesignTool(this);
4654         }
4655         return mDesignTool;
4656     }
4657 
4658     /**
4659      *
4660      */
4661     @Override
4662     public void onViewAdded(View view) {
4663         super.onViewAdded(view);
4664         if (view instanceof MotionHelper) {
4665             MotionHelper helper = (MotionHelper) view;
4666             if (mTransitionListeners == null) {
4667                 mTransitionListeners = new CopyOnWriteArrayList<>();
4668             }
4669             mTransitionListeners.add(helper);
4670 
4671             if (helper.isUsedOnShow()) {
4672                 if (mOnShowHelpers == null) {
4673                     mOnShowHelpers = new ArrayList<>();
4674                 }
4675                 mOnShowHelpers.add(helper);
4676             }
4677             if (helper.isUseOnHide()) {
4678                 if (mOnHideHelpers == null) {
4679                     mOnHideHelpers = new ArrayList<>();
4680                 }
4681                 mOnHideHelpers.add(helper);
4682             }
4683             if (helper.isDecorator()) {
4684                 if (mDecoratorsHelpers == null) {
4685                     mDecoratorsHelpers = new ArrayList<>();
4686                 }
4687                 mDecoratorsHelpers.add(helper);
4688             }
4689         }
4690     }
4691 
4692     /**
4693      *
4694      */
4695     @Override
4696     public void onViewRemoved(View view) {
4697         super.onViewRemoved(view);
4698         if (mOnShowHelpers != null) {
4699             mOnShowHelpers.remove(view);
4700         }
4701         if (mOnHideHelpers != null) {
4702             mOnHideHelpers.remove(view);
4703         }
4704     }
4705 
4706     /**
4707      * Notify OnShow motion helpers
4708      * @param progress
4709      */
4710     public void setOnShow(float progress) {
4711         if (mOnShowHelpers != null) {
4712             final int count = mOnShowHelpers.size();
4713             for (int i = 0; i < count; i++) {
4714                 MotionHelper helper = mOnShowHelpers.get(i);
4715                 helper.setProgress(progress);
4716             }
4717         }
4718     }
4719 
4720     /**
4721      * Notify OnHide motion helpers
4722      * @param progress
4723      */
4724     public void setOnHide(float progress) {
4725         if (mOnHideHelpers != null) {
4726             final int count = mOnHideHelpers.size();
4727             for (int i = 0; i < count; i++) {
4728                 MotionHelper helper = mOnHideHelpers.get(i);
4729                 helper.setProgress(progress);
4730             }
4731         }
4732     }
4733 
4734     /**
4735      * Get the id's of all constraintSets used by MotionLayout
4736      *
4737      * @return
4738      */
4739     public  @IdRes
4740             int[] getConstraintSetIds() {
4741         if (mScene == null) {
4742             return null;
4743         }
4744         return mScene.getConstraintSetIds();
4745     }
4746 
4747     /**
4748      * Get the id's of all constraintSets with the matching types
4749      *
4750      * @return
4751      */
4752     public int[] getMatchingConstraintSetIds(String ... types) {
4753         if (mScene == null) {
4754             return null;
4755         }
4756         return mScene.getMatchingStateLabels(types);
4757     }
4758 
4759     /**
4760      * Get the ConstraintSet associated with an id
4761      * This returns a link to the constraintSet
4762      * But in most cases can be used.
4763      * createConstraintSet makes a copy which is more expensive.
4764      *
4765      * @param id of the constraintSet
4766      * @return ConstraintSet of MotionLayout
4767      * @see #cloneConstraintSet(int)
4768      */
4769     public ConstraintSet getConstraintSet(int id) {
4770         if (mScene == null) {
4771             return null;
4772         }
4773         return mScene.getConstraintSet(id);
4774     }
4775 
4776     /**
4777      * Creates a ConstraintSet based on an existing
4778      * constraintSet.
4779      * This makes a copy of the ConstraintSet.
4780      *
4781      * @param id The ide of the ConstraintSet
4782      * @return the ConstraintSet
4783      */
4784     public ConstraintSet cloneConstraintSet(int id) {
4785         if (mScene == null) {
4786             return null;
4787         }
4788         ConstraintSet orig = mScene.getConstraintSet(id);
4789         ConstraintSet ret = new ConstraintSet();
4790         ret.clone(orig);
4791         return ret;
4792     }
4793 
4794     /**
4795      * rebuild the motion Layouts
4796      *
4797      * @deprecated Please call rebuildScene() instead.
4798      */
4799     @Deprecated
4800     public void rebuildMotion() {
4801         Log.e(TAG, "This method is deprecated. Please call rebuildScene() instead.");
4802         rebuildScene();
4803     }
4804 
4805     /**
4806      * rebuild the motion Layouts
4807      */
4808     public void rebuildScene() {
4809         mModel.reEvaluateState();
4810         invalidate();
4811     }
4812 
4813     /**
4814      * update a ConstraintSet under the id.
4815      *
4816      * @param stateId id of the ConstraintSet
4817      * @param set     The constraintSet
4818      */
4819     public void updateState(int stateId, ConstraintSet set) {
4820         if (mScene != null) {
4821             mScene.setConstraintSet(stateId, set);
4822         }
4823         updateState();
4824         if (mCurrentState == stateId) {
4825             set.applyTo(this);
4826         }
4827     }
4828 
4829     /**
4830      * Update a ConstraintSet but animate the change.
4831      *
4832      * @param stateId  id of the ConstraintSet
4833      * @param set      The constraintSet
4834      * @param duration The length of time to perform the animation
4835      */
4836     public void updateStateAnimate(int stateId, ConstraintSet set, int duration) {
4837         if (mScene == null) {
4838             return;
4839         }
4840 
4841         if (mCurrentState == stateId) {
4842             updateState(R.id.view_transition, getConstraintSet(stateId));
4843             setState(R.id.view_transition, -1, -1);
4844             updateState(stateId, set);
4845             MotionScene.Transition tmpTransition =
4846                     new MotionScene.Transition(-1, mScene, R.id.view_transition, stateId);
4847             tmpTransition.setDuration(duration);
4848             setTransition(tmpTransition);
4849             transitionToEnd();
4850         }
4851     }
4852 
4853     /**
4854      * on completing the current transition, transition to this state.
4855      *
4856      * @param id
4857      */
4858     public void scheduleTransitionTo(int id) {
4859         if (getCurrentState() == -1) {
4860             transitionToState(id);
4861         } else {
4862             if (mScheduledTransitionTo == null) {
4863                 mScheduledTransitionTo = new int[4];
4864             } else if (mScheduledTransitionTo.length <= mScheduledTransitions) {
4865                 mScheduledTransitionTo =
4866                         Arrays.copyOf(mScheduledTransitionTo, mScheduledTransitionTo.length * 2);
4867             }
4868             mScheduledTransitionTo[mScheduledTransitions++] = id;
4869         }
4870     }
4871 
4872     /**
4873      * Not sure we want this
4874      *
4875      *
4876      */
4877     public void updateState() {
4878         mModel.initFrom(mLayoutWidget,
4879                 mScene.getConstraintSet(mBeginState),
4880                 mScene.getConstraintSet(mEndState));
4881         rebuildScene();
4882     }
4883 
4884     /**
4885      * Get all Transitions known to the system.
4886      *
4887      * @return
4888      */
4889     public ArrayList<MotionScene.Transition> getDefinedTransitions() {
4890         if (mScene == null) {
4891             return null;
4892         }
4893         return mScene.getDefinedTransitions();
4894     }
4895 
4896     /**
4897      * Gets the state you are currently transitioning from.
4898      * If you are transitioning from an unknown state returns -1
4899      *
4900      * @return State you are transitioning from.
4901      */
4902     public int getStartState() {
4903         return mBeginState;
4904     }
4905 
4906     /**
4907      * Gets the state you are currently transition to.
4908      *
4909      * @return The State you are transitioning to.
4910      */
4911     public int getEndState() {
4912         return mEndState;
4913     }
4914 
4915     /**
4916      * Gets the position you are animating to typically 0 or 1.
4917      * This is useful during animation after touch up
4918      *
4919      * @return The target position you are moving to
4920      */
4921     public float getTargetPosition() {
4922         return mTransitionGoalPosition;
4923     }
4924 
4925     /**
4926      * Change the current Transition duration.
4927      *
4928      * @param milliseconds duration for transition to complete
4929      */
4930     public void setTransitionDuration(int milliseconds) {
4931         if (mScene == null) {
4932             Log.e(TAG, "MotionScene not defined");
4933             return;
4934         }
4935         mScene.setDuration(milliseconds);
4936     }
4937 
4938     /**
4939      * This returns the internal Transition Structure
4940      *
4941      * @param id
4942      * @return
4943      */
4944     public MotionScene.Transition getTransition(int id) {
4945         return mScene.getTransitionById(id);
4946     }
4947 
4948     /**
4949      * This looks up the constraintset ID given an id string (
4950      *
4951      * @param id String id (without the "@+id/")
4952      * @return the integer id of the string
4953      *
4954      */
4955     int lookUpConstraintId(String id) {
4956         if (mScene == null) {
4957             return 0;
4958         }
4959         return mScene.lookUpConstraintId(id);
4960     }
4961 
4962     /**
4963      * does a revers look up to find the ConstraintSets Name
4964      *
4965      * @param id the integer id of the constraintSet
4966      * @return
4967      */
4968     String getConstraintSetNames(int id) {
4969         if (mScene == null) {
4970             return null;
4971         }
4972         return mScene.lookUpConstraintName(id);
4973     }
4974 
4975     /**
4976      * this allow disabling autoTransitions to prevent design surface from being in undefined states
4977      *
4978      * @param disable
4979      */
4980     void disableAutoTransition(boolean disable) {
4981         if (mScene == null) {
4982             return;
4983         }
4984         mScene.disableAutoTransition(disable);
4985     }
4986 
4987     /**
4988      * Enables (or disables) MotionLayout's onClick and onSwipe handling.
4989      *
4990      * @param enabled If true,  touch & click  is enabled; otherwise it is disabled
4991      */
4992     public void setInteractionEnabled(boolean enabled) {
4993         mInteractionEnabled = enabled;
4994     }
4995 
4996     /**
4997      * Determines whether MotionLayout's touch & click handling are enabled.
4998      * An interaction enabled MotionLayout can respond to user input and initiate and control.
4999      * MotionLayout interactions are enabled initially by default.
5000      * MotionLayout touch & click handling may be enabled or disabled by calling its
5001      * setInteractionEnabled method.
5002      *
5003      * @return true if MotionLayout's  touch & click  is enabled, false otherwise
5004      */
5005     public boolean isInteractionEnabled() {
5006         return mInteractionEnabled;
5007     }
5008 
5009     private void fireTransitionStarted() {
5010         if (mTransitionListener != null) {
5011             mTransitionListener.onTransitionStarted(this, mBeginState, mEndState);
5012         }
5013         if (mTransitionListeners != null) {
5014             for (TransitionListener listeners : mTransitionListeners) {
5015                 listeners.onTransitionStarted(this, mBeginState, mEndState);
5016             }
5017         }
5018     }
5019 
5020     /**
5021      * Execute a ViewTransition.
5022      * Transition will execute if its conditions are met and it is enabled
5023      *
5024      * @param viewTransitionId
5025      * @param view             The views to apply to
5026      */
5027     public void viewTransition(int viewTransitionId, View... view) {
5028         if (mScene != null) {
5029             mScene.viewTransition(viewTransitionId, view);
5030         } else {
5031             Log.e(TAG, " no motionScene");
5032         }
5033     }
5034 
5035     /**
5036      * Enable a ViewTransition ID.
5037      *
5038      * @param viewTransitionId id of ViewTransition
5039      * @param enable           If false view transition cannot be executed.
5040      */
5041     public void enableViewTransition(int viewTransitionId, boolean enable) {
5042         if (mScene != null) {
5043             mScene.enableViewTransition(viewTransitionId, enable);
5044         }
5045     }
5046 
5047     /**
5048      * Is transition id enabled or disabled
5049      *
5050      * @param viewTransitionId the ide of the transition
5051      * @return true if enabled
5052      */
5053     public boolean isViewTransitionEnabled(int viewTransitionId) {
5054         if (mScene != null) {
5055             return mScene.isViewTransitionEnabled(viewTransitionId);
5056         }
5057         return false;
5058     }
5059 
5060     /**
5061      * Apply the view transitions keyFrames to the MotionController.
5062      * Note ConstraintOverride is not used
5063      *
5064      * @param viewTransitionId the id of the view transition
5065      * @param motionController the MotionController to apply the keyframes to
5066      * @return true if it found and applied the viewTransition false otherwise
5067      */
5068     public boolean applyViewTransition(int viewTransitionId, MotionController motionController) {
5069         if (mScene != null) {
5070             return mScene.applyViewTransition(viewTransitionId, motionController);
5071         }
5072         return false;
5073     }
5074 
5075     /**
5076      * Is initial state changes are applied during onAttachedToWindow or after.
5077      * @return
5078      */
5079     public boolean isDelayedApplicationOfInitialState() {
5080         return mDelayedApply;
5081     }
5082 
5083     /**
5084      * Initial state changes are applied during onAttachedToWindow unless this is set to true.
5085      * @param delayedApply
5086      */
5087     public void setDelayedApplicationOfInitialState(boolean delayedApply) {
5088         this.mDelayedApply = delayedApply;
5089     }
5090 
5091 }
5092