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