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