1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.constraintlayout.motion.widget;
18 
19 import static androidx.constraintlayout.motion.widget.Key.UNSET;
20 
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.graphics.RectF;
24 import android.util.Log;
25 import android.util.SparseArray;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.animation.AccelerateDecelerateInterpolator;
29 import android.view.animation.AccelerateInterpolator;
30 import android.view.animation.AnimationUtils;
31 import android.view.animation.BounceInterpolator;
32 import android.view.animation.DecelerateInterpolator;
33 import android.view.animation.Interpolator;
34 import android.view.animation.OvershootInterpolator;
35 
36 import androidx.constraintlayout.core.motion.utils.CurveFit;
37 import androidx.constraintlayout.core.motion.utils.Easing;
38 import androidx.constraintlayout.core.motion.utils.KeyCache;
39 import androidx.constraintlayout.core.motion.utils.SplineSet;
40 import androidx.constraintlayout.core.motion.utils.VelocityMatrix;
41 import androidx.constraintlayout.motion.utils.CustomSupport;
42 import androidx.constraintlayout.motion.utils.ViewOscillator;
43 import androidx.constraintlayout.motion.utils.ViewSpline;
44 import androidx.constraintlayout.motion.utils.ViewState;
45 import androidx.constraintlayout.motion.utils.ViewTimeCycle;
46 import androidx.constraintlayout.widget.ConstraintAttribute;
47 import androidx.constraintlayout.widget.ConstraintLayout;
48 import androidx.constraintlayout.widget.ConstraintSet;
49 
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 
56 /**
57  * Contains the picture of a view through a transition and is used to interpolate it.
58  * During an transition every view has a MotionController which drives its position.
59  * <p>
60  * All parameter which affect a views motion are added to MotionController and then setup()
61  * builds out the splines that control the view.
62  *
63  *
64  */
65 public class MotionController {
66     public static final int PATH_PERCENT = 0;
67     public static final int PATH_PERPENDICULAR = 1;
68     public static final int HORIZONTAL_PATH_X = 2;
69     public static final int HORIZONTAL_PATH_Y = 3;
70     public static final int VERTICAL_PATH_X = 4;
71     public static final int VERTICAL_PATH_Y = 5;
72     public static final int DRAW_PATH_NONE = 0;
73     public static final int DRAW_PATH_BASIC = 1;
74     public static final int DRAW_PATH_RELATIVE = 2;
75     public static final int DRAW_PATH_CARTESIAN = 3;
76     public static final int DRAW_PATH_AS_CONFIGURED = 4;
77     public static final int DRAW_PATH_RECTANGLE = 5;
78     public static final int DRAW_PATH_SCREEN = 6;
79 
80 
81     public static final int ROTATION_RIGHT = 1;
82     public static final int ROTATION_LEFT = 2;
83     Rect mTempRect = new Rect(); // for efficiency
84 
85     private static final String TAG = "MotionController";
86     private static final boolean DEBUG = false;
87     private static final boolean FAVOR_FIXED_SIZE_VIEWS = false;
88     View mView;
89     int mId;
90     boolean mForceMeasure = false;
91     String mConstraintTag;
92     private int mCurveFitType = KeyFrames.UNSET;
93     private MotionPaths mStartMotionPath = new MotionPaths();
94     private MotionPaths mEndMotionPath = new MotionPaths();
95 
96     private MotionConstrainedPoint mStartPoint = new MotionConstrainedPoint();
97     private MotionConstrainedPoint mEndPoint = new MotionConstrainedPoint();
98 
99     private CurveFit[] mSpline; // spline 0 is the one that process all the standard attributes
100     private CurveFit mArcSpline;
101     float mMotionStagger = Float.NaN;
102     float mStaggerOffset = 0;
103     float mStaggerScale = 1.0f;
104     float mCurrentCenterX, mCurrentCenterY;
105     private int[] mInterpolateVariables;
106     private double[] mInterpolateData; // scratch data created during setup
107     private double[] mInterpolateVelocity; // scratch data created during setup
108 
109     private String[] mAttributeNames;  // the names of the custom attributes
110     private int[] mAttributeInterpolatorCount; // how many interpolators for each custom attribute
111     private int mMaxDimension = 4;
112     private float []mValuesBuff = new float[mMaxDimension];
113     private ArrayList<MotionPaths> mMotionPaths = new ArrayList<>();
114     private float[] mVelocity = new float[1]; // used as a temp buffer to return values
115 
116     private ArrayList<Key> mKeyList = new ArrayList<>(); // List of key frame items
117     private HashMap<String, ViewTimeCycle> mTimeCycleAttributesMap; // splines for use TimeCycles
118     private HashMap<String, ViewSpline> mAttributesMap; // splines to calculate values of attributes
119     private HashMap<String, ViewOscillator> mCycleMap; // splines to calculate values of attributes
120     private KeyTrigger[] mKeyTriggers; // splines to calculate values of attributes
121     private int mPathMotionArc = UNSET;
122     private int mTransformPivotTarget = UNSET; // if set, pivot point is set to the other object
123     private View mTransformPivotView = null; // if set, pivot point is set to the other object
124     private int mQuantizeMotionSteps = UNSET;
125     private float mQuantizeMotionPhase = Float.NaN;
126     private Interpolator mQuantizeMotionInterpolator = null;
127     private boolean mNoMovement = false;
128 
129     /**
130      * Get the view to pivot around
131      *
132      * @return id of view or UNSET if not set
133      */
getTransformPivotTarget()134     public int getTransformPivotTarget() {
135         return mTransformPivotTarget;
136     }
137 
138     /**
139      * Set a view to pivot around
140      *
141      * @param transformPivotTarget id of view
142      */
setTransformPivotTarget(int transformPivotTarget)143     public void setTransformPivotTarget(int transformPivotTarget) {
144         mTransformPivotTarget = transformPivotTarget;
145         mTransformPivotView = null;
146     }
147 
getKeyFrame(int i)148     MotionPaths getKeyFrame(int i) {
149         return mMotionPaths.get(i);
150     }
151 
MotionController(View view)152     MotionController(View view) {
153         setView(view);
154     }
155 
156     /**
157      * get the left most position of the widget at the start of the movement.
158      *
159      * @return the left most position
160      */
getStartX()161     public float getStartX() {
162         return mStartMotionPath.mX;
163     }
164 
165     /**
166      * get the top most position of the widget at the start of the movement.
167      * Positive is down.
168      *
169      * @return the top most position
170      */
getStartY()171     public float getStartY() {
172         return mStartMotionPath.mY;
173     }
174 
175     /**
176      * get the left most position of the widget at the end of the movement.
177      *
178      * @return the left most position
179      */
getFinalX()180     public float getFinalX() {
181         return mEndMotionPath.mX;
182     }
183 
184     /**
185      * get the top most position of the widget at the end of the movement.
186      * Positive is down.
187      *
188      * @return the top most position
189      */
getFinalY()190     public float getFinalY() {
191         return mEndMotionPath.mY;
192     }
193 
194     /**
195      * get the width of the widget at the start of the movement.
196      *
197      * @return the width at the start
198      */
getStartWidth()199     public float getStartWidth() {
200         return mStartMotionPath.mWidth;
201     }
202 
203     /**
204      * get the width of the widget at the start of the movement.
205      *
206      * @return the height at the start
207      */
getStartHeight()208     public float getStartHeight() {
209         return mStartMotionPath.mHeight;
210     }
211 
212     /**
213      * get the width of the widget at the end of the movement.
214      *
215      * @return the width at the end
216      */
getFinalWidth()217     public float getFinalWidth() {
218         return mEndMotionPath.mWidth;
219     }
220 
221     /**
222      * get the width of the widget at the end of the movement.
223      *
224      * @return the height at the end
225      */
getFinalHeight()226     public float getFinalHeight() {
227         return mEndMotionPath.mHeight;
228     }
229 
230     /**
231      * Will return the id of the view to move relative to.
232      * The position at the start and then end will be viewed relative to this view
233      * -1 is the return value if NOT in polar mode
234      *
235      * @return the view id of the view this is in polar mode to or -1 if not in polar
236      */
getAnimateRelativeTo()237     public int getAnimateRelativeTo() {
238         return mStartMotionPath.mAnimateRelativeTo;
239     }
240 
241     /**
242      * This ties one motionController to another to allow relative pathes
243      * @param motionController
244      */
setupRelative(MotionController motionController)245     public void setupRelative(MotionController motionController) {
246         mStartMotionPath.setupRelative(motionController, motionController.mStartMotionPath);
247         mEndMotionPath.setupRelative(motionController, motionController.mEndMotionPath);
248     }
249 
250     /**
251      * Get the center X of the motion at the current progress
252      * @return
253      */
getCenterX()254     public float getCenterX() {
255         return mCurrentCenterX;
256     }
257 
258     /**
259      * Get the center Y of the motion at the current progress
260      * @return
261      */
getCenterY()262     public float getCenterY() {
263         return mCurrentCenterY;
264     }
265 
266     /**
267      * Get a center and velocities at the position p
268      * @param p
269      * @param pos
270      * @param vel
271      */
getCenter(double p, float[] pos, float[] vel)272     public void getCenter(double p, float[] pos, float[] vel) {
273         double[] position = new double[4];
274         double[] velocity = new double[4];
275         @SuppressWarnings("unused") int[] temp = new int[4];
276         mSpline[0].getPos(p, position);
277         mSpline[0].getSlope(p, velocity);
278         Arrays.fill(vel, 0);
279         mStartMotionPath.getCenter(p, mInterpolateVariables, position, pos, velocity, vel);
280     }
281 
282     /**
283      * During the next layout call measure then layout
284      */
remeasure()285     public void remeasure() {
286         mForceMeasure = true;
287     }
288 
289     /**
290      * fill the array point with the center coordinates.
291      * point[0] is filled with the
292      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
293      * 1.0
294      *
295      * @param points     array to fill (should be 2x the number of mPoints
296      * @param pointCount
297      * @return number of key frames
298      */
buildPath(float[] points, int pointCount)299     void buildPath(float[] points, int pointCount) {
300         float mils = 1.0f / (pointCount - 1);
301         SplineSet trans_x = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_X);
302         SplineSet trans_y = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_Y);
303         ViewOscillator osc_x = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_X);
304         ViewOscillator osc_y = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_Y);
305 
306         for (int i = 0; i < pointCount; i++) {
307             float position = i * mils;
308             if (mStaggerScale != 1.0f) {
309                 if (position < mStaggerOffset) {
310                     position = 0;
311                 }
312                 if (position > mStaggerOffset && position < 1.0) {
313                     position -= mStaggerOffset;
314                     position *= mStaggerScale;
315                     position = Math.min(position, 1.0f);
316                 }
317             }
318             double p = position;
319 
320             Easing easing = mStartMotionPath.mKeyFrameEasing;
321             float start = 0;
322             float end = Float.NaN;
323             for (MotionPaths frame : mMotionPaths) {
324                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
325                     if (frame.mTime < position) {  // frame with easing is before the current pos
326                         easing = frame.mKeyFrameEasing; // this is the candidate
327                         start = frame.mTime; // this is also the starting time
328                     } else { // frame with easing is past the pos
329                         if (Float.isNaN(end)) { // we never ended the time line
330                             end = frame.mTime;
331                         }
332                     }
333                 }
334             }
335 
336             if (easing != null) {
337                 if (Float.isNaN(end)) {
338                     end = 1.0f;
339                 }
340                 float offset = (position - start) / (end - start);
341                 offset = (float) easing.get(offset);
342                 p = offset * (end - start) + start;
343 
344             }
345 
346             mSpline[0].getPos(p, mInterpolateData);
347             if (mArcSpline != null) {
348                 if (mInterpolateData.length > 0) {
349                     mArcSpline.getPos(p, mInterpolateData);
350                 }
351             }
352             mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData,
353                     points, i * 2);
354 
355             if (osc_x != null) {
356                 points[i * 2] += osc_x.get(position);
357             } else if (trans_x != null) {
358                 points[i * 2] += trans_x.get(position);
359             }
360             if (osc_y != null) {
361                 points[i * 2 + 1] += osc_y.get(position);
362             } else if (trans_y != null) {
363                 points[i * 2 + 1] += trans_y.get(position);
364             }
365         }
366     }
367 
getPos(double position)368     double[] getPos(double position) {
369         mSpline[0].getPos(position, mInterpolateData);
370         if (mArcSpline != null) {
371             if (mInterpolateData.length > 0) {
372                 mArcSpline.getPos(position, mInterpolateData);
373             }
374         }
375         return mInterpolateData;
376     }
377 
378     /**
379      * fill the array point with the center coordinates.
380      * point[0] is filled with the
381      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
382      * 1.0
383      *
384      * @param bounds     array to fill (should be 2x the number of mPoints
385      * @param pointCount
386      * @return number of key frames
387      */
buildBounds(float[] bounds, int pointCount)388     void buildBounds(float[] bounds, int pointCount) {
389         float mils = 1.0f / (pointCount - 1);
390         @SuppressWarnings("unused")
391         SplineSet trans_x = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_X);
392         @SuppressWarnings("unused")
393         SplineSet trans_y = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_Y);
394         @SuppressWarnings("unused")
395         ViewOscillator osc_x = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_X);
396         @SuppressWarnings("unused")
397         ViewOscillator osc_y = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_Y);
398 
399         for (int i = 0; i < pointCount; i++) {
400             float position = i * mils;
401             if (mStaggerScale != 1.0f) {
402                 if (position < mStaggerOffset) {
403                     position = 0;
404                 }
405                 if (position > mStaggerOffset && position < 1.0) {
406                     position -= mStaggerOffset;
407                     position *= mStaggerScale;
408                     position = Math.min(position, 1.0f);
409                 }
410             }
411             double p = position;
412 
413             Easing easing = mStartMotionPath.mKeyFrameEasing;
414             float start = 0;
415             float end = Float.NaN;
416             for (MotionPaths frame : mMotionPaths) {
417                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
418                     if (frame.mTime < position) {  // frame with easing is before the current pos
419                         easing = frame.mKeyFrameEasing; // this is the candidate
420                         start = frame.mTime; // this is also the starting time
421                     } else { // frame with easing is past the pos
422                         if (Float.isNaN(end)) { // we never ended the time line
423                             end = frame.mTime;
424                         }
425                     }
426                 }
427             }
428 
429             if (easing != null) {
430                 if (Float.isNaN(end)) {
431                     end = 1.0f;
432                 }
433                 float offset = (position - start) / (end - start);
434                 offset = (float) easing.get(offset);
435                 p = offset * (end - start) + start;
436 
437             }
438 
439             mSpline[0].getPos(p, mInterpolateData);
440             if (mArcSpline != null) {
441                 if (mInterpolateData.length > 0) {
442                     mArcSpline.getPos(p, mInterpolateData);
443                 }
444             }
445             mStartMotionPath.getBounds(mInterpolateVariables, mInterpolateData, bounds, i * 2);
446         }
447     }
448 
getPreCycleDistance()449     private float getPreCycleDistance() {
450         int pointCount = 100;
451         float[] points = new float[2];
452         float sum = 0;
453         float mils = 1.0f / (pointCount - 1);
454         double x = 0, y = 0;
455         for (int i = 0; i < pointCount; i++) {
456             float position = i * mils;
457 
458             double p = position;
459 
460             Easing easing = mStartMotionPath.mKeyFrameEasing;
461             float start = 0;
462             float end = Float.NaN;
463             for (MotionPaths frame : mMotionPaths) {
464                 if (frame.mKeyFrameEasing != null) { // this frame has an easing
465                     if (frame.mTime < position) {  // frame with easing is before the current pos
466                         easing = frame.mKeyFrameEasing; // this is the candidate
467                         start = frame.mTime; // this is also the starting time
468                     } else { // frame with easing is past the pos
469                         if (Float.isNaN(end)) { // we never ended the time line
470                             end = frame.mTime;
471                         }
472                     }
473                 }
474             }
475 
476             if (easing != null) {
477                 if (Float.isNaN(end)) {
478                     end = 1.0f;
479                 }
480                 float offset = (position - start) / (end - start);
481                 offset = (float) easing.get(offset);
482                 p = offset * (end - start) + start;
483 
484             }
485 
486             mSpline[0].getPos(p, mInterpolateData);
487             mStartMotionPath.getCenter(p, mInterpolateVariables, mInterpolateData, points, 0);
488             if (i > 0) {
489                 sum += (float) Math.hypot(y - points[1], x - points[0]);
490             }
491             x = points[0];
492             y = points[1];
493         }
494         return sum;
495     }
496 
getPositionKeyframe(int layoutWidth, int layoutHeight, float x, float y)497     KeyPositionBase getPositionKeyframe(int layoutWidth, int layoutHeight, float x, float y) {
498         RectF start = new RectF();
499         start.left = mStartMotionPath.mX;
500         start.top = mStartMotionPath.mY;
501         start.right = start.left + mStartMotionPath.mWidth;
502         start.bottom = start.top + mStartMotionPath.mHeight;
503         RectF end = new RectF();
504         end.left = mEndMotionPath.mX;
505         end.top = mEndMotionPath.mY;
506         end.right = end.left + mEndMotionPath.mWidth;
507         end.bottom = end.top + mEndMotionPath.mHeight;
508         for (Key key : mKeyList) {
509             if (key instanceof KeyPositionBase) {
510                 if (((KeyPositionBase) key).intersects(layoutWidth, layoutHeight,
511                         start, end, x, y)) {
512                     return (KeyPositionBase) key;
513                 }
514             }
515         }
516         return null;
517     }
518 
buildKeyFrames(float[] keyFrames, int[] mode)519     int buildKeyFrames(float[] keyFrames, int[] mode) {
520         if (keyFrames != null) {
521             int count = 0;
522             double[] time = mSpline[0].getTimePoints();
523             if (mode != null) {
524                 for (MotionPaths keyFrame : mMotionPaths) {
525                     mode[count++] = keyFrame.mMode;
526                 }
527                 count = 0;
528             }
529 
530             for (int i = 0; i < time.length; i++) {
531                 mSpline[0].getPos(time[i], mInterpolateData);
532                 mStartMotionPath.getCenter(time[i], mInterpolateVariables, mInterpolateData,
533                         keyFrames, count);
534                 count += 2;
535             }
536             return count / 2;
537         }
538         return 0;
539     }
540 
buildKeyBounds(float[] keyBounds, int[] mode)541     int buildKeyBounds(float[] keyBounds, int[] mode) {
542         if (keyBounds != null) {
543             int count = 0;
544             double[] time = mSpline[0].getTimePoints();
545             if (mode != null) {
546                 for (MotionPaths keyFrame : mMotionPaths) {
547                     mode[count++] = keyFrame.mMode;
548                 }
549                 count = 0;
550             }
551 
552             for (int i = 0; i < time.length; i++) {
553                 mSpline[0].getPos(time[i], mInterpolateData);
554                 mStartMotionPath.getBounds(mInterpolateVariables, mInterpolateData,
555                         keyBounds, count);
556                 count += 2;
557             }
558             return count / 2;
559         }
560         return 0;
561     }
562 
563     String[] mAttributeTable;
564 
getAttributeValues(String attributeType, float[] points, int pointCount)565     int getAttributeValues(String attributeType, float[] points, int pointCount) {
566         @SuppressWarnings("unused") float mils = 1.0f / (pointCount - 1);
567         SplineSet spline = mAttributesMap.get(attributeType);
568         if (spline == null) {
569             return -1;
570         }
571         for (int j = 0; j < points.length; j++) {
572             points[j] = spline.get(j / (points.length - 1));
573         }
574         return points.length;
575     }
576 
buildRect(float p, float[] path, int offset)577     void buildRect(float p, float[] path, int offset) {
578         p = getAdjustedPosition(p, null);
579         mSpline[0].getPos(p, mInterpolateData);
580         mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, offset);
581     }
582 
buildRectangles(float[] path, int pointCount)583     void buildRectangles(float[] path, int pointCount) {
584         float mils = 1.0f / (pointCount - 1);
585         for (int i = 0; i < pointCount; i++) {
586             float position = i * mils;
587             position = getAdjustedPosition(position, null);
588             mSpline[0].getPos(position, mInterpolateData);
589             mStartMotionPath.getRect(mInterpolateVariables, mInterpolateData, path, i * 8);
590         }
591     }
592 
getKeyFrameParameter(int type, float x, float y)593     float getKeyFrameParameter(int type, float x, float y) {
594 
595         float dx = mEndMotionPath.mX - mStartMotionPath.mX;
596         float dy = mEndMotionPath.mY - mStartMotionPath.mY;
597         float startCenterX = mStartMotionPath.mX + mStartMotionPath.mWidth / 2;
598         float startCenterY = mStartMotionPath.mY + mStartMotionPath.mHeight / 2;
599         float hypotenuse = (float) Math.hypot(dx, dy);
600         if (hypotenuse < 0.0000001) {
601             return Float.NaN;
602         }
603 
604         float vx = x - startCenterX;
605         float vy = y - startCenterY;
606         float distFromStart = (float) Math.hypot(vx, vy);
607         if (distFromStart == 0) {
608             return 0;
609         }
610         float pathDistance = (vx * dx + vy * dy);
611 
612         switch (type) {
613             case PATH_PERCENT:
614                 return pathDistance / hypotenuse;
615             case PATH_PERPENDICULAR:
616                 return (float) Math.sqrt(hypotenuse * hypotenuse - pathDistance * pathDistance);
617             case HORIZONTAL_PATH_X:
618                 return vx / dx;
619             case HORIZONTAL_PATH_Y:
620                 return vy / dx;
621             case VERTICAL_PATH_X:
622                 return vx / dy;
623             case VERTICAL_PATH_Y:
624                 return vy / dy;
625         }
626         return 0;
627     }
628 
insertKey(MotionPaths point)629     private void insertKey(MotionPaths point) {
630         int pos = Collections.binarySearch(mMotionPaths, point);
631         if (pos == 0) {
632             Log.e(TAG, " KeyPath position \"" + point.mPosition + "\" outside of range");
633         }
634         mMotionPaths.add(-pos - 1, point);
635     }
636 
addKeys(ArrayList<Key> list)637     void addKeys(ArrayList<Key> list) {
638         mKeyList.addAll(list);
639         if (DEBUG) {
640             for (Key key : mKeyList) {
641                 Log.v(TAG, " ################ set = " + key.getClass().getSimpleName());
642             }
643         }
644     }
645 
646     /**
647      * Add a key to the MotionController
648      * @param key
649      */
addKey(Key key)650     public void addKey(Key key) {
651         mKeyList.add(key);
652         if (DEBUG) {
653             Log.v(TAG, " ################ addKey = " + key.getClass().getSimpleName());
654         }
655     }
656 
setPathMotionArc(int arc)657     public void setPathMotionArc(int arc) {
658         mPathMotionArc = arc;
659     }
660 
661     /**
662      * Called after all TimePoints & Cycles have been added;
663      * Spines are evaluated
664      */
setup(int parentWidth, int parentHeight, float transitionDuration, long currentTime)665     public void setup(int parentWidth,
666                       int parentHeight,
667                       float transitionDuration,
668                       long currentTime) {
669         HashSet<String> springAttributes = new HashSet<>(); // attributes we need to interpolate
670         HashSet<String> timeCycleAttributes = new HashSet<>(); // attributes we need to interpolate
671         HashSet<String> splineAttributes = new HashSet<>(); // attributes we need to interpolate
672         HashSet<String> cycleAttributes = new HashSet<>(); // attributes we need to oscillate
673         HashMap<String, Integer> interpolation = new HashMap<>();
674         ArrayList<KeyTrigger> triggerList = null;
675         if (DEBUG) {
676             if (mKeyList == null) {
677                 Log.v(TAG, ">>>>>>>>>>>>>>> mKeyList==null");
678 
679             } else {
680                 Log.v(TAG, ">>>>>>>>>>>>>>> mKeyList for " + Debug.getName(mView));
681 
682             }
683         }
684 
685         if (mPathMotionArc != UNSET) {
686             mStartMotionPath.mPathMotionArc = mPathMotionArc;
687         }
688 
689         mStartPoint.different(mEndPoint, splineAttributes);
690         if (DEBUG) {
691             HashSet<String> attr = new HashSet<>();
692             mStartPoint.different(mEndPoint, attr);
693             Log.v(TAG, ">>>>>>>>>>>>>>> MotionConstrainedPoint found "
694                     + Arrays.toString(attr.toArray()));
695         }
696         if (mKeyList != null) {
697             for (Key key : mKeyList) {
698                 if (key instanceof KeyPosition) {
699                     KeyPosition keyPath = (KeyPosition) key;
700                     insertKey(new MotionPaths(parentWidth, parentHeight, keyPath,
701                             mStartMotionPath, mEndMotionPath));
702                     if (keyPath.mCurveFit != UNSET) {
703                         mCurveFitType = keyPath.mCurveFit;
704                     }
705                 } else if (key instanceof KeyCycle) {
706                     key.getAttributeNames(cycleAttributes);
707                 } else if (key instanceof KeyTimeCycle) {
708                     key.getAttributeNames(timeCycleAttributes);
709                 } else if (key instanceof KeyTrigger) {
710                     if (triggerList == null) {
711                         triggerList = new ArrayList<>();
712                     }
713                     triggerList.add((KeyTrigger) key);
714                 } else {
715                     key.setInterpolation(interpolation);
716                     key.getAttributeNames(splineAttributes);
717                 }
718             }
719         }
720 
721         //--------------------------- trigger support --------------------
722 
723         if (triggerList != null) {
724             mKeyTriggers = triggerList.toArray(new KeyTrigger[0]);
725         }
726 
727         if (DEBUG) {
728             if (!cycleAttributes.isEmpty()) {
729                 Log.v(TAG, ">>>>>>>>>>>>>>>>  found cycleA"
730                         + Debug.getName(mView) + " cycles     "
731                         + Arrays.toString(cycleAttributes.toArray()));
732             }
733             if (!splineAttributes.isEmpty()) {
734                 Log.v(TAG, ">>>>>>>>>>>>>>>>  found spline "
735                         + Debug.getName(mView) + " attrs      "
736                         + Arrays.toString(splineAttributes.toArray()));
737             }
738             if (!timeCycleAttributes.isEmpty()) {
739                 Log.v(TAG, ">>>>>>>>>>>>>>>>  found timeCycle "
740                         + Debug.getName(mView) + " attrs      "
741                         + Arrays.toString(timeCycleAttributes.toArray()));
742             }
743             if (!springAttributes.isEmpty()) {
744                 Log.v(TAG, ">>>>>>>>>>>>>>>>  found springs "
745                         + Debug.getName(mView) + " attrs      "
746                         + Arrays.toString(springAttributes.toArray()));
747             }
748 
749         }
750 
751         //--------------------------- splines support --------------------
752         if (!splineAttributes.isEmpty()) {
753             mAttributesMap = new HashMap<>();
754             for (String attribute : splineAttributes) {
755                 ViewSpline splineSets;
756                 if (attribute.startsWith("CUSTOM,")) {
757                     SparseArray<ConstraintAttribute> attrList = new SparseArray<>();
758                     String customAttributeName = attribute.split(",")[1];
759                     for (Key key : mKeyList) {
760                         if (key.mCustomConstraints == null) {
761                             continue;
762                         }
763                         ConstraintAttribute customAttribute =
764                                 key.mCustomConstraints.get(customAttributeName);
765                         if (customAttribute != null) {
766                             attrList.append(key.mFramePosition, customAttribute);
767                         }
768                     }
769                     splineSets = ViewSpline.makeCustomSpline(attribute, attrList);
770                 } else {
771                     splineSets = ViewSpline.makeSpline(attribute);
772                 }
773                 if (splineSets == null) {
774                     continue;
775                 }
776                 splineSets.setType(attribute);
777                 mAttributesMap.put(attribute, splineSets);
778             }
779             if (mKeyList != null) {
780                 for (Key key : mKeyList) {
781                     if ((key instanceof KeyAttributes)) {
782                         key.addValues(mAttributesMap);
783                     }
784                 }
785             }
786             mStartPoint.addValues(mAttributesMap, 0);
787             mEndPoint.addValues(mAttributesMap, 100);
788 
789             for (String spline : mAttributesMap.keySet()) {
790                 int curve = CurveFit.SPLINE; // default is SPLINE
791                 if (interpolation.containsKey(spline)) {
792                     Integer boxedCurve = interpolation.get(spline);
793                     if (boxedCurve != null) {
794                         curve = boxedCurve;
795                     }
796                 }
797                 SplineSet splineSet = mAttributesMap.get(spline);
798                 if (splineSet != null) {
799                     splineSet.setup(curve);
800                 }
801             }
802         }
803 
804         //--------------------------- timeCycle support --------------------
805         if (!timeCycleAttributes.isEmpty()) {
806             if (mTimeCycleAttributesMap == null) {
807                 mTimeCycleAttributesMap = new HashMap<>();
808             }
809             for (String attribute : timeCycleAttributes) {
810                 if (mTimeCycleAttributesMap.containsKey(attribute)) {
811                     continue;
812                 }
813 
814                 ViewTimeCycle splineSets = null;
815                 if (attribute.startsWith("CUSTOM,")) {
816                     SparseArray<ConstraintAttribute> attrList = new SparseArray<>();
817                     String customAttributeName = attribute.split(",")[1];
818                     for (Key key : mKeyList) {
819                         if (key.mCustomConstraints == null) {
820                             continue;
821                         }
822                         ConstraintAttribute customAttribute =
823                                 key.mCustomConstraints.get(customAttributeName);
824                         if (customAttribute != null) {
825                             attrList.append(key.mFramePosition, customAttribute);
826                         }
827                     }
828                     splineSets = ViewTimeCycle.makeCustomSpline(attribute, attrList);
829                 } else {
830                     splineSets = ViewTimeCycle.makeSpline(attribute, currentTime);
831 
832                 }
833                 if (splineSets == null) {
834                     continue;
835                 }
836                 splineSets.setType(attribute);
837                 mTimeCycleAttributesMap.put(attribute, splineSets);
838             }
839 
840             if (mKeyList != null) {
841                 for (Key key : mKeyList) {
842                     if (key instanceof KeyTimeCycle) {
843                         ((KeyTimeCycle) key).addTimeValues(mTimeCycleAttributesMap);
844                     }
845                 }
846             }
847 
848             for (String spline : mTimeCycleAttributesMap.keySet()) {
849                 int curve = CurveFit.SPLINE; // default is SPLINE
850                 if (interpolation.containsKey(spline)) {
851                     curve = interpolation.get(spline);
852                 }
853                 mTimeCycleAttributesMap.get(spline).setup(curve);
854             }
855         }
856 
857         //--------------------------------- end new key frame 2
858 
859         MotionPaths[] points = new MotionPaths[2 + mMotionPaths.size()];
860         int count = 1;
861         points[0] = mStartMotionPath;
862         points[points.length - 1] = mEndMotionPath;
863         if (mMotionPaths.size() > 0 && mCurveFitType == KeyFrames.UNSET) {
864             mCurveFitType = CurveFit.SPLINE;
865         }
866         for (MotionPaths point : mMotionPaths) {
867             points[count++] = point;
868         }
869 
870         // -----  setup custom attributes which must be in the start and end constraint sets
871         int variables = 18;
872         HashSet<String> attributeNameSet = new HashSet<>();
873         for (String s : mEndMotionPath.mAttributes.keySet()) {
874             if (mStartMotionPath.mAttributes.containsKey(s)) {
875                 if (!splineAttributes.contains("CUSTOM," + s)) {
876                     attributeNameSet.add(s);
877                 }
878             }
879         }
880 
881         mAttributeNames = attributeNameSet.toArray(new String[0]);
882         mAttributeInterpolatorCount = new int[mAttributeNames.length];
883         for (int i = 0; i < mAttributeNames.length; i++) {
884             String attributeName = mAttributeNames[i];
885             mAttributeInterpolatorCount[i] = 0;
886             for (int j = 0; j < points.length; j++) {
887                 if (points[j].mAttributes.containsKey(attributeName)) {
888                     ConstraintAttribute attribute = points[j].mAttributes.get(attributeName);
889                     if (attribute != null) {
890                         mAttributeInterpolatorCount[i] += attribute.numberOfInterpolatedValues();
891                         break;
892                     }
893                 }
894             }
895         }
896         boolean arcMode = points[0].mPathMotionArc != UNSET;
897         boolean[] mask = new boolean[variables + mAttributeNames.length]; // defaults to false
898         for (int i = 1; i < points.length; i++) {
899             points[i].different(points[i - 1], mask, mAttributeNames, arcMode);
900         }
901 
902         count = 0;
903         for (int i = 1; i < mask.length; i++) {
904             if (mask[i]) {
905                 count++;
906             }
907         }
908 
909         mInterpolateVariables = new int[count];
910         int varLen = Math.max(2, count);
911         mInterpolateData = new double[varLen];
912         mInterpolateVelocity = new double[varLen];
913 
914         count = 0;
915         for (int i = 1; i < mask.length; i++) {
916             if (mask[i]) {
917                 mInterpolateVariables[count++] = i;
918             }
919         }
920 
921         double[][] splineData = new double[points.length][mInterpolateVariables.length];
922         double[] timePoint = new double[points.length];
923 
924         for (int i = 0; i < points.length; i++) {
925             points[i].fillStandard(splineData[i], mInterpolateVariables);
926             timePoint[i] = points[i].mTime;
927         }
928 
929         for (int j = 0; j < mInterpolateVariables.length; j++) {
930             int interpolateVariable = mInterpolateVariables[j];
931             if (interpolateVariable < MotionPaths.sNames.length) {
932                 @SuppressWarnings("unused")
933                 String s = MotionPaths.sNames[mInterpolateVariables[j]] + " [";
934                 for (int i = 0; i < points.length; i++) {
935                     s += splineData[i][j];
936                 }
937             }
938         }
939         mSpline = new CurveFit[1 + mAttributeNames.length];
940 
941         for (int i = 0; i < mAttributeNames.length; i++) {
942             int pointCount = 0;
943             double[][] splinePoints = null;
944             double[] timePoints = null;
945             String name = mAttributeNames[i];
946 
947             for (int j = 0; j < points.length; j++) {
948                 if (points[j].hasCustomData(name)) {
949                     if (splinePoints == null) {
950                         timePoints = new double[points.length];
951                         splinePoints =
952                                 new double[points.length][points[j].getCustomDataCount(name)];
953                     }
954                     timePoints[pointCount] = points[j].mTime;
955                     points[j].getCustomData(name, splinePoints[pointCount], 0);
956                     pointCount++;
957                 }
958             }
959             timePoints = Arrays.copyOf(timePoints, pointCount);
960             splinePoints = Arrays.copyOf(splinePoints, pointCount);
961             mSpline[i + 1] = CurveFit.get(mCurveFitType, timePoints, splinePoints);
962         }
963 
964         mSpline[0] = CurveFit.get(mCurveFitType, timePoint, splineData);
965         // --------------------------- SUPPORT ARC MODE --------------
966         if (points[0].mPathMotionArc != UNSET) {
967             int size = points.length;
968             int[] mode = new int[size];
969             double[] time = new double[size];
970             double[][] values = new double[size][2];
971             for (int i = 0; i < size; i++) {
972                 mode[i] = points[i].mPathMotionArc;
973                 time[i] = points[i].mTime;
974                 values[i][0] = points[i].mX;
975                 values[i][1] = points[i].mY;
976             }
977 
978             mArcSpline = CurveFit.getArc(mode, time, values);
979         }
980 
981         //--------------------------- Cycle support --------------------
982         float distance = Float.NaN;
983         mCycleMap = new HashMap<>();
984         if (mKeyList != null) {
985             for (String attribute : cycleAttributes) {
986                 ViewOscillator cycle = ViewOscillator.makeSpline(attribute);
987                 if (cycle == null) {
988                     continue;
989                 }
990 
991                 if (cycle.variesByPath()) {
992                     if (Float.isNaN(distance)) {
993                         distance = getPreCycleDistance();
994                     }
995                 }
996                 cycle.setType(attribute);
997                 mCycleMap.put(attribute, cycle);
998             }
999             for (Key key : mKeyList) {
1000                 if (key instanceof KeyCycle) {
1001                     ((KeyCycle) key).addCycleValues(mCycleMap);
1002                 }
1003             }
1004             for (ViewOscillator cycle : mCycleMap.values()) {
1005                 cycle.setup(distance);
1006             }
1007         }
1008 
1009         if (DEBUG) {
1010             Log.v(TAG, "Animation of splineAttributes "
1011                     + Arrays.toString(splineAttributes.toArray()));
1012             Log.v(TAG, "Animation of cycle " + Arrays.toString(mCycleMap.keySet().toArray()));
1013             if (mAttributesMap != null) {
1014                 Log.v(TAG, " splines = "
1015                         + Arrays.toString(mAttributesMap.keySet().toArray()));
1016                 for (String s : mAttributesMap.keySet()) {
1017                     Log.v(TAG, s + " = " + mAttributesMap.get(s));
1018                 }
1019             }
1020             Log.v(TAG, " ---------------------------------------- ");
1021         }
1022 
1023         //--------------------------- end cycle support ----------------
1024     }
1025 
1026     /**
1027      * Debug string
1028      *
1029      * @return
1030      */
1031     @Override
toString()1032     public String toString() {
1033         return " start: x: " + mStartMotionPath.mX + " y: " + mStartMotionPath.mY
1034                 + " end: x: " + mEndMotionPath.mX + " y: " + mEndMotionPath.mY;
1035     }
1036 
readView(MotionPaths motionPaths)1037     private void readView(MotionPaths motionPaths) {
1038         motionPaths.setBounds((int) mView.getX(), (int) mView.getY(),
1039                 mView.getWidth(), mView.getHeight());
1040     }
1041 
1042     // @TODO: add description
1043 
1044     /**
1045      *
1046      * @param view
1047      */
setView(View view)1048     public void setView(View view) {
1049         mView = view;
1050         mId = view.getId();
1051         ViewGroup.LayoutParams lp = view.getLayoutParams();
1052         if (lp instanceof ConstraintLayout.LayoutParams) {
1053             mConstraintTag = ((ConstraintLayout.LayoutParams) lp).getConstraintTag();
1054         }
1055     }
1056 
1057     /**
1058      * Get the view that is being controlled
1059      * @return
1060      */
getView()1061     public View getView() {
1062         return mView;
1063     }
1064 
setStartCurrentState(View v)1065     void setStartCurrentState(View v) {
1066         mStartMotionPath.mTime = 0;
1067         mStartMotionPath.mPosition = 0;
1068         mStartMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
1069         mStartPoint.setState(v);
1070     }
1071 
1072     /**
1073      * configure the position of the view
1074      * @param rect
1075      * @param v
1076      * @param rotation
1077      * @param preWidth
1078      * @param preHeight
1079      */
setStartState(ViewState rect, View v, int rotation, int preWidth, int preHeight)1080     public void setStartState(ViewState rect, View v, int rotation, int preWidth, int preHeight) {
1081         mStartMotionPath.mTime = 0;
1082         mStartMotionPath.mPosition = 0;
1083         int cx, cy;
1084         Rect r = new Rect();
1085         switch (rotation) {
1086             case 2:
1087                 cx = rect.left + rect.right;
1088                 cy = rect.top + rect.bottom;
1089                 r.left = preHeight - (cy + rect.width()) / 2;
1090                 r.top = (cx - rect.height()) / 2;
1091                 r.right = r.left + rect.width();
1092                 r.bottom = r.top + rect.height();
1093                 break;
1094             case 1:
1095                 cx = rect.left + rect.right;
1096                 cy = rect.top + rect.bottom;
1097                 r.left = (cy - rect.width()) / 2;
1098                 r.top = preWidth - (cx + rect.height()) / 2;
1099                 r.right = r.left + rect.width();
1100                 r.bottom = r.top + rect.height();
1101                 break;
1102         }
1103         mStartMotionPath.setBounds(r.left, r.top, r.width(), r.height());
1104         mStartPoint.setState(r, v, rotation, rect.rotation);
1105     }
1106 
rotate(Rect rect, Rect out, int rotation, int preHeight, int preWidth)1107     void rotate(Rect rect, Rect out, int rotation, int preHeight, int preWidth) {
1108         int cx, cy;
1109         switch (rotation) {
1110 
1111             case ConstraintSet.ROTATE_PORTRATE_OF_LEFT:
1112                 cx = rect.left + rect.right;
1113                 cy = rect.top + rect.bottom;
1114                 out.left = preHeight - (cy + rect.width()) / 2;
1115                 out.top = (cx - rect.height()) / 2;
1116                 out.right = out.left + rect.width();
1117                 out.bottom = out.top + rect.height();
1118                 break;
1119             case ConstraintSet.ROTATE_PORTRATE_OF_RIGHT:
1120                 cx = rect.left + rect.right;
1121                 cy = rect.top + rect.bottom;
1122                 out.left = (cy - rect.width()) / 2;
1123                 out.top = preWidth - (cx + rect.height()) / 2;
1124                 out.right = out.left + rect.width();
1125                 out.bottom = out.top + rect.height();
1126                 break;
1127             case ConstraintSet.ROTATE_LEFT_OF_PORTRATE:
1128                 cx = rect.left + rect.right;
1129                 cy = rect.bottom + rect.top;
1130                 out.left = preHeight - (cy + rect.width()) / 2;
1131                 out.top = (cx - rect.height()) / 2;
1132                 out.right = out.left + rect.width();
1133                 out.bottom = out.top + rect.height();
1134                 break;
1135             case ConstraintSet.ROTATE_RIGHT_OF_PORTRATE:
1136                 cx = rect.left + rect.right;
1137                 cy = rect.top + rect.bottom;
1138                 out.left = rect.height() / 2 + rect.top - cx / 2;
1139                 out.top = preWidth - (cx + rect.height()) / 2;
1140                 out.right = out.left + rect.width();
1141                 out.bottom = out.top + rect.height();
1142                 break;
1143         }
1144     }
1145 
setStartState(Rect cw, ConstraintSet constraintSet, int parentWidth, int parentHeight)1146     void setStartState(Rect cw, ConstraintSet constraintSet, int parentWidth, int parentHeight) {
1147         int rotate = constraintSet.mRotate; // for rotated frames
1148         if (rotate != 0) {
1149             rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
1150         }
1151         mStartMotionPath.mTime = 0;
1152         mStartMotionPath.mPosition = 0;
1153         readView(mStartMotionPath);
1154         mStartMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
1155         ConstraintSet.Constraint constraint = constraintSet.getParameters(mId);
1156         mStartMotionPath.applyParameters(constraint);
1157         mMotionStagger = constraint.motion.mMotionStagger;
1158         mStartPoint.setState(cw, constraintSet, rotate, mId);
1159         mTransformPivotTarget = constraint.transform.transformPivotTarget;
1160         mQuantizeMotionSteps = constraint.motion.mQuantizeMotionSteps;
1161         mQuantizeMotionPhase = constraint.motion.mQuantizeMotionPhase;
1162         mQuantizeMotionInterpolator = getInterpolator(mView.getContext(),
1163                 constraint.motion.mQuantizeInterpolatorType,
1164                 constraint.motion.mQuantizeInterpolatorString,
1165                 constraint.motion.mQuantizeInterpolatorID
1166         );
1167     }
1168 
1169     static final int EASE_IN_OUT = 0;
1170     static final int EASE_IN = 1;
1171     static final int EASE_OUT = 2;
1172     static final int LINEAR = 3;
1173     static final int BOUNCE = 4;
1174     static final int OVERSHOOT = 5;
1175     private static final int SPLINE_STRING = -1;
1176     private static final int INTERPOLATOR_REFERENCE_ID = -2;
1177     private static final int INTERPOLATOR_UNDEFINED = -3;
1178 
getInterpolator(Context context, int type, String interpolatorString, int id)1179     private static Interpolator getInterpolator(Context context,
1180                                                 int type,
1181                                                 String interpolatorString,
1182                                                 int id) {
1183         switch (type) {
1184             case SPLINE_STRING:
1185                 final Easing easing = Easing.getInterpolator(interpolatorString);
1186                 return new Interpolator() {
1187                     @Override
1188                     public float getInterpolation(float v) {
1189                         return (float) easing.get(v);
1190                     }
1191                 };
1192             case INTERPOLATOR_REFERENCE_ID:
1193                 return AnimationUtils.loadInterpolator(context, id);
1194             case EASE_IN_OUT:
1195                 return new AccelerateDecelerateInterpolator();
1196             case EASE_IN:
1197                 return new AccelerateInterpolator();
1198             case EASE_OUT:
1199                 return new DecelerateInterpolator();
1200             case LINEAR:
1201                 return null;
1202             case BOUNCE:
1203                 return new BounceInterpolator();
1204             case OVERSHOOT:
1205                 return new OvershootInterpolator();
1206             case INTERPOLATOR_UNDEFINED:
1207                 return null;
1208         }
1209         return null;
1210     }
1211 
1212     void setEndState(Rect cw, ConstraintSet constraintSet, int parentWidth, int parentHeight) {
1213         int rotate = constraintSet.mRotate; // for rotated frames
1214         if (rotate != 0) {
1215             rotate(cw, mTempRect, rotate, parentWidth, parentHeight);
1216             cw = mTempRect;
1217         }
1218         mEndMotionPath.mTime = 1;
1219         mEndMotionPath.mPosition = 1;
1220         readView(mEndMotionPath);
1221         mEndMotionPath.setBounds(cw.left, cw.top, cw.width(), cw.height());
1222         mEndMotionPath.applyParameters(constraintSet.getParameters(mId));
1223         mEndPoint.setState(cw, constraintSet, rotate, mId);
1224     }
1225 
1226     void setBothStates(View v) {
1227         mStartMotionPath.mTime = 0;
1228         mStartMotionPath.mPosition = 0;
1229         mNoMovement = true;
1230         mStartMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
1231         mEndMotionPath.setBounds(v.getX(), v.getY(), v.getWidth(), v.getHeight());
1232         mStartPoint.setState(v);
1233         mEndPoint.setState(v);
1234     }
1235 
1236     /**
1237      * Calculates the adjusted position (and optional velocity).
1238      * Note if requesting velocity staggering is not considered
1239      *
1240      * @param position position pre stagger
1241      * @param velocity return velocity
1242      * @return actual position accounting for easing and staggering
1243      */
1244     private float getAdjustedPosition(float position, float[] velocity) {
1245         if (velocity != null) {
1246             velocity[0] = 1;
1247         } else if (mStaggerScale != 1.0) {
1248             if (position < mStaggerOffset) {
1249                 position = 0;
1250             }
1251             if (position > mStaggerOffset && position < 1.0) {
1252                 position -= mStaggerOffset;
1253                 position *= mStaggerScale;
1254                 position = Math.min(position, 1.0f);
1255             }
1256         }
1257 
1258         // adjust the position based on the easing curve
1259         float adjusted = position;
1260         Easing easing = mStartMotionPath.mKeyFrameEasing;
1261         float start = 0;
1262         float end = Float.NaN;
1263         for (MotionPaths frame : mMotionPaths) {
1264             if (frame.mKeyFrameEasing != null) { // this frame has an easing
1265                 if (frame.mTime < position) {  // frame with easing is before the current pos
1266                     easing = frame.mKeyFrameEasing; // this is the candidate
1267                     start = frame.mTime; // this is also the starting time
1268                 } else { // frame with easing is past the pos
1269                     if (Float.isNaN(end)) { // we never ended the time line
1270                         end = frame.mTime;
1271                     }
1272                 }
1273             }
1274         }
1275 
1276         if (easing != null) {
1277             if (Float.isNaN(end)) {
1278                 end = 1.0f;
1279             }
1280             float offset = (position - start) / (end - start);
1281             float new_offset = (float) easing.get(offset);
1282             adjusted = new_offset * (end - start) + start;
1283             if (velocity != null) {
1284                 velocity[0] = (float) easing.getDiff(offset);
1285             }
1286         }
1287         return adjusted;
1288     }
1289 
1290     void endTrigger(boolean start) {
1291         if ("button".equals(Debug.getName(mView))) {
1292             if (mKeyTriggers != null) {
1293                 for (int i = 0; i < mKeyTriggers.length; i++) {
1294                     mKeyTriggers[i].conditionallyFire(start ? -100 : 100, mView);
1295                 }
1296             }
1297         }
1298     }
1299 
1300     /**
1301      * The main driver of interpolation
1302      *
1303      * @param child
1304      * @param globalPosition
1305      * @param time
1306      * @param keyCache
1307      * @return do you need to keep animating
1308      */
1309     boolean interpolate(View child, float globalPosition, long time, KeyCache keyCache) {
1310         boolean timeAnimation = false;
1311         float position = getAdjustedPosition(globalPosition, null);
1312         // This quantize the position into steps e.g. 4 steps = 0-0.25,0.25-0.50 etc
1313         if (mQuantizeMotionSteps != UNSET) {
1314             float steps = 1.0f / mQuantizeMotionSteps; // the length of a step
1315             float jump = (float) Math.floor(position / steps) * steps; // step jumps
1316             float section = (position % steps) / steps; // float from 0 to 1 in a step
1317 
1318             if (!Float.isNaN(mQuantizeMotionPhase)) {
1319                 section = (section + mQuantizeMotionPhase) % 1;
1320             }
1321             if (mQuantizeMotionInterpolator != null) {
1322                 section = mQuantizeMotionInterpolator.getInterpolation(section);
1323             } else {
1324                 section = section > 0.5 ? 1 : 0;
1325             }
1326             position = section * steps + jump;
1327         }
1328         ViewTimeCycle.PathRotate timePathRotate = null;
1329         if (mAttributesMap != null) {
1330             for (ViewSpline aSpline : mAttributesMap.values()) {
1331                 aSpline.setProperty(child, position);
1332             }
1333         }
1334 
1335         if (mTimeCycleAttributesMap != null) {
1336             for (ViewTimeCycle aSpline : mTimeCycleAttributesMap.values()) {
1337                 if (aSpline instanceof ViewTimeCycle.PathRotate) {
1338                     timePathRotate = (ViewTimeCycle.PathRotate) aSpline;
1339                     continue;
1340                 }
1341                 timeAnimation |= aSpline.setProperty(child, position, time, keyCache);
1342             }
1343         }
1344 
1345         if (mSpline != null) {
1346             mSpline[0].getPos(position, mInterpolateData);
1347             mSpline[0].getSlope(position, mInterpolateVelocity);
1348             if (mArcSpline != null) {
1349                 if (mInterpolateData.length > 0) {
1350                     mArcSpline.getPos(position, mInterpolateData);
1351                     mArcSpline.getSlope(position, mInterpolateVelocity);
1352                 }
1353             }
1354 
1355             if (!mNoMovement) {
1356                 mStartMotionPath.setView(position, child,
1357                         mInterpolateVariables, mInterpolateData, mInterpolateVelocity,
1358                         null, mForceMeasure);
1359                 mForceMeasure = false;
1360             }
1361             if (mTransformPivotTarget != UNSET) {
1362                 if (mTransformPivotView == null) {
1363                     View layout = (View) child.getParent();
1364                     mTransformPivotView = layout.findViewById(mTransformPivotTarget);
1365                 }
1366                 if (mTransformPivotView != null) {
1367                     float cy = (mTransformPivotView.getTop()
1368                             + mTransformPivotView.getBottom()) / 2.0f;
1369                     float cx = (mTransformPivotView.getLeft()
1370                             + mTransformPivotView.getRight()) / 2.0f;
1371                     if (child.getRight() - child.getLeft() > 0
1372                             && child.getBottom() - child.getTop() > 0) {
1373                         float px = (cx - child.getLeft());
1374                         float py = (cy - child.getTop());
1375                         child.setPivotX(px);
1376                         child.setPivotY(py);
1377                     }
1378                 }
1379             }
1380 
1381             if (mAttributesMap != null) {
1382                 for (SplineSet aSpline : mAttributesMap.values()) {
1383                     if (aSpline instanceof ViewSpline.PathRotate
1384                             && mInterpolateVelocity.length > 1) {
1385                         ((ViewSpline.PathRotate) aSpline).setPathRotate(child, position,
1386                                 mInterpolateVelocity[0], mInterpolateVelocity[1]);
1387                     }
1388                 }
1389 
1390             }
1391             if (timePathRotate != null) {
1392                 timeAnimation |= timePathRotate.setPathRotate(child, keyCache, position, time,
1393                         mInterpolateVelocity[0], mInterpolateVelocity[1]);
1394             }
1395 
1396             for (int i = 1; i < mSpline.length; i++) {
1397                 CurveFit spline = mSpline[i];
1398                 spline.getPos(position, mValuesBuff);
1399                 CustomSupport.setInterpolatedValue(mStartMotionPath
1400                         .mAttributes.get(mAttributeNames[i - 1]), child, mValuesBuff);
1401 
1402             }
1403             if (mStartPoint.mVisibilityMode == ConstraintSet.VISIBILITY_MODE_NORMAL) {
1404                 if (position <= 0.0f) {
1405                     child.setVisibility(mStartPoint.mVisibility);
1406                 } else if (position >= 1.0f) {
1407                     child.setVisibility(mEndPoint.mVisibility);
1408                 } else if (mEndPoint.mVisibility != mStartPoint.mVisibility) {
1409                     child.setVisibility(View.VISIBLE);
1410                 }
1411             }
1412 
1413             if (mKeyTriggers != null) {
1414                 for (int i = 0; i < mKeyTriggers.length; i++) {
1415                     mKeyTriggers[i].conditionallyFire(position, child);
1416                 }
1417             }
1418         } else {
1419             // do the interpolation
1420 
1421             float float_l = (mStartMotionPath.mX
1422                     + (mEndMotionPath.mX - mStartMotionPath.mX) * position);
1423             float float_t = (mStartMotionPath.mY
1424                     + (mEndMotionPath.mY - mStartMotionPath.mY) * position);
1425             float float_width = (mStartMotionPath.mWidth
1426                     + (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
1427             float float_height = (mStartMotionPath.mHeight
1428                     + (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
1429             int l = (int) (0.5f + float_l);
1430             int t = (int) (0.5f + float_t);
1431             int r = (int) (0.5f + float_l + float_width);
1432             int b = (int) (0.5f + float_t + float_height);
1433             int width = r - l;
1434             int height = b - t;
1435 
1436             if (FAVOR_FIXED_SIZE_VIEWS) {
1437                 l = (int) (mStartMotionPath.mX
1438                         + (mEndMotionPath.mX - mStartMotionPath.mX) * position);
1439                 t = (int) (mStartMotionPath.mY
1440                         + (mEndMotionPath.mY - mStartMotionPath.mY) * position);
1441                 width = (int) (mStartMotionPath.mWidth
1442                         + (mEndMotionPath.mWidth - mStartMotionPath.mWidth) * position);
1443                 height = (int) (mStartMotionPath.mHeight
1444                         + (mEndMotionPath.mHeight - mStartMotionPath.mHeight) * position);
1445                 r = l + width;
1446                 b = t + height;
1447             }
1448             if (mEndMotionPath.mWidth != mStartMotionPath.mWidth
1449                     || mEndMotionPath.mHeight != mStartMotionPath.mHeight || mForceMeasure) {
1450                 int widthMeasureSpec =
1451                         View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
1452                 int heightMeasureSpec =
1453                         View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
1454                 child.measure(widthMeasureSpec, heightMeasureSpec);
1455                 mForceMeasure = false;
1456             }
1457             child.layout(l, t, r, b);
1458         }
1459 
1460         if (mCycleMap != null) {
1461             for (ViewOscillator osc : mCycleMap.values()) {
1462                 if (osc instanceof ViewOscillator.PathRotateSet) {
1463                     ((ViewOscillator.PathRotateSet) osc).setPathRotate(child, position,
1464                             mInterpolateVelocity[0], mInterpolateVelocity[1]);
1465                 } else {
1466                     osc.setProperty(child, position);
1467                 }
1468             }
1469         }
1470         return timeAnimation;
1471     }
1472 
1473     /**
1474      * This returns the differential with respect to the animation layout position (Progress)
1475      * of a point on the view (post layout effects are not computed)
1476      *
1477      * @param position    position in time
1478      * @param locationX   the x location on the view (0 = left edge, 1 = right edge)
1479      * @param locationY   the y location on the view (0 = top, 1 = bottom)
1480      * @param mAnchorDpDt returns the differential of the motion with respect to the position
1481      */
1482     void getDpDt(float position, float locationX, float locationY, float[] mAnchorDpDt) {
1483         position = getAdjustedPosition(position, mVelocity);
1484 
1485         if (mSpline != null) {
1486             mSpline[0].getSlope(position, mInterpolateVelocity);
1487             mSpline[0].getPos(position, mInterpolateData);
1488             float v = mVelocity[0];
1489             for (int i = 0; i < mInterpolateVelocity.length; i++) {
1490                 mInterpolateVelocity[i] *= v;
1491             }
1492 
1493             if (mArcSpline != null) {
1494                 if (mInterpolateData.length > 0) {
1495                     mArcSpline.getPos(position, mInterpolateData);
1496                     mArcSpline.getSlope(position, mInterpolateVelocity);
1497                     mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1498                             mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1499                 }
1500                 return;
1501             }
1502             mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1503                     mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1504             return;
1505         }
1506         // do the interpolation
1507         float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
1508         float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
1509         float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
1510         float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
1511         float dRight = dleft + dWidth;
1512         float dBottom = dTop + dHeight;
1513         mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
1514         mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
1515     }
1516 
1517     /**
1518      * This returns the differential with respect to the animation post layout transform
1519      * of a point on the view
1520      *
1521      * @param position    position in time
1522      * @param width       width of the view
1523      * @param height      height of the view
1524      * @param locationX   the x location on the view (0 = left edge, 1 = right edge)
1525      * @param locationY   the y location on the view (0 = top, 1 = bottom)
1526      * @param mAnchorDpDt returns the differential of the motion with respect to the position
1527      */
1528     void getPostLayoutDvDp(float position,
1529                            int width,
1530                            int height,
1531                            float locationX,
1532                            float locationY,
1533                            float[] mAnchorDpDt) {
1534         if (DEBUG) {
1535             Log.v(TAG, " position= " + position + " location= "
1536                     + locationX + " , " + locationY);
1537         }
1538         position = getAdjustedPosition(position, mVelocity);
1539 
1540         SplineSet trans_x = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_X);
1541         SplineSet trans_y = (mAttributesMap == null) ? null : mAttributesMap.get(Key.TRANSLATION_Y);
1542         SplineSet rotation = (mAttributesMap == null) ? null : mAttributesMap.get(Key.ROTATION);
1543         SplineSet scale_x = (mAttributesMap == null) ? null : mAttributesMap.get(Key.SCALE_X);
1544         SplineSet scale_y = (mAttributesMap == null) ? null : mAttributesMap.get(Key.SCALE_Y);
1545 
1546         ViewOscillator osc_x = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_X);
1547         ViewOscillator osc_y = (mCycleMap == null) ? null : mCycleMap.get(Key.TRANSLATION_Y);
1548         ViewOscillator osc_r = (mCycleMap == null) ? null : mCycleMap.get(Key.ROTATION);
1549         ViewOscillator osc_sx = (mCycleMap == null) ? null : mCycleMap.get(Key.SCALE_X);
1550         ViewOscillator osc_sy = (mCycleMap == null) ? null : mCycleMap.get(Key.SCALE_Y);
1551 
1552         VelocityMatrix vmat = new VelocityMatrix();
1553         vmat.clear();
1554         vmat.setRotationVelocity(rotation, position);
1555         vmat.setTranslationVelocity(trans_x, trans_y, position);
1556         vmat.setScaleVelocity(scale_x, scale_y, position);
1557         vmat.setRotationVelocity(osc_r, position);
1558         vmat.setTranslationVelocity(osc_x, osc_y, position);
1559         vmat.setScaleVelocity(osc_sx, osc_sy, position);
1560         if (mArcSpline != null) {
1561             if (mInterpolateData.length > 0) {
1562                 mArcSpline.getPos(position, mInterpolateData);
1563                 mArcSpline.getSlope(position, mInterpolateVelocity);
1564                 mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1565                         mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1566             }
1567             vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1568             return;
1569         }
1570         if (mSpline != null) {
1571             position = getAdjustedPosition(position, mVelocity);
1572             mSpline[0].getSlope(position, mInterpolateVelocity);
1573             mSpline[0].getPos(position, mInterpolateData);
1574             float v = mVelocity[0];
1575             for (int i = 0; i < mInterpolateVelocity.length; i++) {
1576                 mInterpolateVelocity[i] *= v;
1577             }
1578             mStartMotionPath.setDpDt(locationX, locationY, mAnchorDpDt,
1579                     mInterpolateVariables, mInterpolateVelocity, mInterpolateData);
1580             vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1581             return;
1582         }
1583 
1584         // do the interpolation
1585         float dleft = (mEndMotionPath.mX - mStartMotionPath.mX);
1586         float dTop = (mEndMotionPath.mY - mStartMotionPath.mY);
1587         float dWidth = (mEndMotionPath.mWidth - mStartMotionPath.mWidth);
1588         float dHeight = (mEndMotionPath.mHeight - mStartMotionPath.mHeight);
1589         float dRight = dleft + dWidth;
1590         float dBottom = dTop + dHeight;
1591         mAnchorDpDt[0] = dleft * (1 - locationX) + dRight * locationX;
1592         mAnchorDpDt[1] = dTop * (1 - locationY) + dBottom * locationY;
1593 
1594         vmat.clear();
1595         vmat.setRotationVelocity(rotation, position);
1596         vmat.setTranslationVelocity(trans_x, trans_y, position);
1597         vmat.setScaleVelocity(scale_x, scale_y, position);
1598         vmat.setRotationVelocity(osc_r, position);
1599         vmat.setTranslationVelocity(osc_x, osc_y, position);
1600         vmat.setScaleVelocity(osc_sx, osc_sy, position);
1601         vmat.applyTransform(locationX, locationY, width, height, mAnchorDpDt);
1602     }
1603 
1604     /**
1605      * returns the draw path mode
1606      * @return
1607      */
1608     public int getDrawPath() {
1609         int mode = mStartMotionPath.mDrawPath;
1610         for (MotionPaths keyFrame : mMotionPaths) {
1611             mode = Math.max(mode, keyFrame.mDrawPath);
1612         }
1613         mode = Math.max(mode, mEndMotionPath.mDrawPath);
1614         return mode;
1615     }
1616 
1617     public void setDrawPath(int debugMode) {
1618         mStartMotionPath.mDrawPath = debugMode;
1619     }
1620 
1621     String name() {
1622         Context context = mView.getContext();
1623         return context.getResources().getResourceEntryName(mView.getId());
1624     }
1625 
1626     void positionKeyframe(View view,
1627                           KeyPositionBase key,
1628                           float x,
1629                           float y,
1630                           String[] attribute,
1631                           float[] value) {
1632         RectF start = new RectF();
1633         start.left = mStartMotionPath.mX;
1634         start.top = mStartMotionPath.mY;
1635         start.right = start.left + mStartMotionPath.mWidth;
1636         start.bottom = start.top + mStartMotionPath.mHeight;
1637         RectF end = new RectF();
1638         end.left = mEndMotionPath.mX;
1639         end.top = mEndMotionPath.mY;
1640         end.right = end.left + mEndMotionPath.mWidth;
1641         end.bottom = end.top + mEndMotionPath.mHeight;
1642         key.positionAttributes(view, start, end, x, y, attribute, value);
1643     }
1644 
1645     /**
1646      * Get the keyFrames for the view controlled by this MotionController
1647      *
1648      * @param type is position(0-100) + 1000*mType(1=Attr, 2=Pos, 3=TimeCycle 4=Cycle 5=Trigger
1649      * @param pos  the x&y position of the keyFrame along the path
1650      * @return Number of keyFrames found
1651      */
1652     public int getKeyFramePositions(int[] type, float[] pos) {
1653         int i = 0;
1654         int count = 0;
1655         for (Key key : mKeyList) {
1656             type[i++] = key.mFramePosition + 1000 * key.mType;
1657             float time = key.mFramePosition / 100.0f;
1658             mSpline[0].getPos(time, mInterpolateData);
1659             mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, count);
1660             count += 2;
1661         }
1662 
1663         return i;
1664     }
1665 
1666     /**
1667      * Get the keyFrames for the view controlled by this MotionController.
1668      * the info data structure is of the form
1669      * 0 length if your are at index i the [i+len+1] is the next entry
1670      * 1 type  1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
1671      * 2 position
1672      * 3 x location
1673      * 4 y location
1674      * 5
1675      * ...
1676      * length
1677      *
1678      * @param type if type is -1, skip all keyframes with type != -1
1679      * @param info is a data structure array of int that holds info on each keyframe
1680      * @return Number of keyFrames found
1681      */
1682     public int getKeyFrameInfo(int type, int[] info) {
1683         int count = 0;
1684         int cursor = 0;
1685         float[] pos = new float[2];
1686         int len;
1687         for (Key key : mKeyList) {
1688             if (key.mType != type && type == -1) {
1689                 continue;
1690             }
1691             len = cursor;
1692             info[cursor] = 0;
1693 
1694             info[++cursor] = key.mType;
1695             info[++cursor] = key.mFramePosition;
1696 
1697             float time = key.mFramePosition / 100.0f;
1698             mSpline[0].getPos(time, mInterpolateData);
1699             mStartMotionPath.getCenter(time, mInterpolateVariables, mInterpolateData, pos, 0);
1700             info[++cursor] = Float.floatToIntBits(pos[0]);
1701             info[++cursor] = Float.floatToIntBits(pos[1]);
1702             if (key instanceof KeyPosition) {
1703                 KeyPosition kp = (KeyPosition) key;
1704                 info[++cursor] = kp.mPositionType;
1705 
1706                 info[++cursor] = Float.floatToIntBits(kp.mPercentX);
1707                 info[++cursor] = Float.floatToIntBits(kp.mPercentY);
1708             }
1709             cursor++;
1710             info[len] = cursor - len;
1711             count++;
1712         }
1713 
1714         return count;
1715     }
1716 }
1717