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 17 package androidx.constraintlayout.core.state; 18 19 import androidx.annotation.RestrictTo; 20 import androidx.constraintlayout.core.motion.CustomVariable; 21 import androidx.constraintlayout.core.motion.Motion; 22 import androidx.constraintlayout.core.motion.MotionWidget; 23 import androidx.constraintlayout.core.motion.key.MotionKeyAttributes; 24 import androidx.constraintlayout.core.motion.key.MotionKeyCycle; 25 import androidx.constraintlayout.core.motion.key.MotionKeyPosition; 26 import androidx.constraintlayout.core.motion.utils.Easing; 27 import androidx.constraintlayout.core.motion.utils.KeyCache; 28 import androidx.constraintlayout.core.motion.utils.SpringStopEngine; 29 import androidx.constraintlayout.core.motion.utils.StopEngine; 30 import androidx.constraintlayout.core.motion.utils.StopLogicEngine; 31 import androidx.constraintlayout.core.motion.utils.TypedBundle; 32 import androidx.constraintlayout.core.motion.utils.TypedValues; 33 import androidx.constraintlayout.core.motion.utils.Utils; 34 import androidx.constraintlayout.core.widgets.ConstraintWidget; 35 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer; 36 37 import org.jspecify.annotations.NonNull; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.HashMap; 42 43 public class Transition implements TypedValues { 44 private static final boolean DEBUG = false; 45 public static final int START = 0; 46 public static final int END = 1; 47 public static final int INTERPOLATED = 2; 48 static final int EASE_IN_OUT = 0; 49 static final int EASE_IN = 1; 50 static final int EASE_OUT = 2; 51 static final int LINEAR = 3; 52 static final int BOUNCE = 4; 53 static final int OVERSHOOT = 5; 54 static final int ANTICIPATE = 6; 55 private static final int SPLINE_STRING = -1; 56 @SuppressWarnings("unused") 57 private static final int INTERPOLATOR_REFERENCE_ID = -2; 58 private HashMap<Integer, HashMap<String, KeyPosition>> mKeyPositions = new HashMap<>(); 59 private HashMap<String, WidgetState> mState = new HashMap<>(); 60 private TypedBundle mBundle = new TypedBundle(); 61 // Interpolation 62 private int mDefaultInterpolator = 0; 63 private String mDefaultInterpolatorString = null; 64 private Easing mEasing = null; 65 private int mAutoTransition = 0; 66 private int mDuration = 400; 67 private float mStagger = 0.0f; 68 private OnSwipe mOnSwipe = null; 69 final CorePixelDp mToPixel; // Todo placed here as a temp till the refactor is done 70 int mParentStartWidth, mParentStartHeight; 71 int mParentEndWidth, mParentEndHeight; 72 int mParentInterpolatedWidth, mParentInterpolateHeight; 73 boolean mWrap; 74 Transition(@onNull CorePixelDp dpToPixel)75 public Transition(@NonNull CorePixelDp dpToPixel) { 76 mToPixel = dpToPixel; 77 } 78 79 // @TODO: add description 80 @SuppressWarnings("HiddenTypeParameter") createOnSwipe()81 OnSwipe createOnSwipe() { 82 return mOnSwipe = new OnSwipe(); 83 } 84 85 // @TODO: add description hasOnSwipe()86 public boolean hasOnSwipe() { 87 return mOnSwipe != null; 88 } 89 90 static class OnSwipe { 91 String mAnchorId; 92 private int mAnchorSide; 93 private StopEngine mEngine; 94 public static final int ANCHOR_SIDE_TOP = 0; 95 public static final int ANCHOR_SIDE_LEFT = 1; 96 public static final int ANCHOR_SIDE_RIGHT = 2; 97 public static final int ANCHOR_SIDE_BOTTOM = 3; 98 public static final int ANCHOR_SIDE_MIDDLE = 4; 99 public static final int ANCHOR_SIDE_START = 5; 100 public static final int ANCHOR_SIDE_END = 6; 101 public static final String[] SIDES = {"top", "left", "right", 102 "bottom", "middle", "start", "end"}; 103 private static final float[][] TOUCH_SIDES = { 104 {0.5f, 0.0f}, // top 105 {0.0f, 0.5f}, // left 106 {1.0f, 0.5f}, // right 107 {0.5f, 1.0f}, // bottom 108 {0.5f, 0.5f}, // middle 109 {0.0f, 0.5f}, // start TODO (dynamically updated) 110 {1.0f, 0.5f}, // end TODO (dynamically updated) 111 }; 112 113 @SuppressWarnings("unused") 114 private String mRotationCenterId; 115 String mLimitBoundsTo; 116 @SuppressWarnings("unused") 117 private boolean mDragVertical = true; 118 private int mDragDirection = 0; 119 public static final int DRAG_UP = 0; 120 public static final int DRAG_DOWN = 1; 121 public static final int DRAG_LEFT = 2; 122 public static final int DRAG_RIGHT = 3; 123 public static final int DRAG_START = 4; 124 public static final int DRAG_END = 5; 125 public static final int DRAG_CLOCKWISE = 6; 126 public static final int DRAG_ANTICLOCKWISE = 7; 127 public static final String[] DIRECTIONS = {"up", "down", "left", "right", "start", 128 "end", "clockwise", "anticlockwise"}; 129 130 private float mDragScale = 1; 131 @SuppressWarnings("unused") 132 private float mDragThreshold = 10; 133 private int mAutoCompleteMode = 0; 134 public static final int MODE_CONTINUOUS_VELOCITY = 0; 135 public static final int MODE_SPRING = 1; 136 public static final String[] MODE = {"velocity", "spring"}; 137 private float mMaxVelocity = 4.f; 138 private float mMaxAcceleration = 1.2f; 139 140 // On touch up what happens 141 private int mOnTouchUp = 0; 142 public static final int ON_UP_AUTOCOMPLETE = 0; 143 public static final int ON_UP_AUTOCOMPLETE_TO_START = 1; 144 public static final int ON_UP_AUTOCOMPLETE_TO_END = 2; 145 public static final int ON_UP_STOP = 3; 146 public static final int ON_UP_DECELERATE = 4; 147 public static final int ON_UP_DECELERATE_AND_COMPLETE = 5; 148 public static final int ON_UP_NEVER_COMPLETE_TO_START = 6; 149 public static final int ON_UP_NEVER_COMPLETE_TO_END = 7; 150 public static final String[] TOUCH_UP = {"autocomplete", "toStart", 151 "toEnd", "stop", "decelerate", "decelerateComplete", 152 "neverCompleteStart", "neverCompleteEnd"}; 153 154 private float mSpringMass = 1; 155 private float mSpringStiffness = 400; 156 private float mSpringDamping = 10; 157 private float mSpringStopThreshold = 0.01f; 158 private float mDestination = 0.0f; 159 160 // In spring mode what happens at the boundary 161 private int mSpringBoundary = 0; 162 public static final int BOUNDARY_OVERSHOOT = 0; 163 public static final int BOUNDARY_BOUNCE_START = 1; 164 public static final int BOUNDARY_BOUNCE_END = 2; 165 public static final int BOUNDARY_BOUNCE_BOTH = 3; 166 public static final String[] BOUNDARY = {"overshoot", "bounceStart", 167 "bounceEnd", "bounceBoth"}; 168 169 private static final float[][] TOUCH_DIRECTION = { 170 {0.0f, -1.0f}, // up 171 {0.0f, 1.0f}, // down 172 {-1.0f, 0.0f}, // left 173 {1.0f, 0.0f}, // right 174 {-1.0f, 0.0f}, // start (dynamically updated) 175 {1.0f, 0.0f}, // end (dynamically updated) 176 }; 177 private long mStart; 178 getScale()179 float getScale() { 180 return mDragScale; 181 } 182 getDirection()183 float[] getDirection() { 184 return TOUCH_DIRECTION[mDragDirection]; 185 } 186 getSide()187 float[] getSide() { 188 return TOUCH_SIDES[mAnchorSide]; 189 } 190 setAnchorId(String anchorId)191 void setAnchorId(String anchorId) { 192 this.mAnchorId = anchorId; 193 } 194 setAnchorSide(int anchorSide)195 void setAnchorSide(int anchorSide) { 196 this.mAnchorSide = anchorSide; 197 } 198 setRotationCenterId(String rotationCenterId)199 void setRotationCenterId(String rotationCenterId) { 200 this.mRotationCenterId = rotationCenterId; 201 } 202 setLimitBoundsTo(String limitBoundsTo)203 void setLimitBoundsTo(String limitBoundsTo) { 204 this.mLimitBoundsTo = limitBoundsTo; 205 } 206 setDragDirection(int dragDirection)207 void setDragDirection(int dragDirection) { 208 this.mDragDirection = dragDirection; 209 mDragVertical = (mDragDirection < 2); 210 } 211 setDragScale(float dragScale)212 void setDragScale(float dragScale) { 213 if (Float.isNaN(dragScale)) { 214 return; 215 } 216 this.mDragScale = dragScale; 217 } 218 setDragThreshold(float dragThreshold)219 void setDragThreshold(float dragThreshold) { 220 if (Float.isNaN(dragThreshold)) { 221 return; 222 } 223 this.mDragThreshold = dragThreshold; 224 } 225 setAutoCompleteMode(int mAutoCompleteMode)226 void setAutoCompleteMode(int mAutoCompleteMode) { 227 this.mAutoCompleteMode = mAutoCompleteMode; 228 } 229 setMaxVelocity(float maxVelocity)230 void setMaxVelocity(float maxVelocity) { 231 if (Float.isNaN(maxVelocity)) { 232 return; 233 } 234 this.mMaxVelocity = maxVelocity; 235 } 236 setMaxAcceleration(float maxAcceleration)237 void setMaxAcceleration(float maxAcceleration) { 238 if (Float.isNaN(maxAcceleration)) { 239 return; 240 } 241 this.mMaxAcceleration = maxAcceleration; 242 } 243 setOnTouchUp(int onTouchUp)244 void setOnTouchUp(int onTouchUp) { 245 this.mOnTouchUp = onTouchUp; 246 } 247 setSpringMass(float mSpringMass)248 void setSpringMass(float mSpringMass) { 249 if (Float.isNaN(mSpringMass)) { 250 return; 251 } 252 this.mSpringMass = mSpringMass; 253 } 254 setSpringStiffness(float mSpringStiffness)255 void setSpringStiffness(float mSpringStiffness) { 256 if (Float.isNaN(mSpringStiffness)) { 257 return; 258 } 259 this.mSpringStiffness = mSpringStiffness; 260 } 261 setSpringDamping(float mSpringDamping)262 void setSpringDamping(float mSpringDamping) { 263 if (Float.isNaN(mSpringDamping)) { 264 return; 265 } 266 this.mSpringDamping = mSpringDamping; 267 } 268 setSpringStopThreshold(float mSpringStopThreshold)269 void setSpringStopThreshold(float mSpringStopThreshold) { 270 if (Float.isNaN(mSpringStopThreshold)) { 271 return; 272 } 273 this.mSpringStopThreshold = mSpringStopThreshold; 274 } 275 setSpringBoundary(int mSpringBoundary)276 void setSpringBoundary(int mSpringBoundary) { 277 this.mSpringBoundary = mSpringBoundary; 278 } 279 getDestinationPosition(float currentPosition, float velocity, float duration)280 float getDestinationPosition(float currentPosition, float velocity, float duration) { 281 float rest = currentPosition + 0.5f * Math.abs(velocity) * velocity / mMaxAcceleration; 282 switch (mOnTouchUp) { 283 case ON_UP_AUTOCOMPLETE_TO_START: 284 if (currentPosition >= 1f) { 285 return 1; 286 } 287 return 0; 288 case ON_UP_NEVER_COMPLETE_TO_END: 289 return 0; 290 case ON_UP_AUTOCOMPLETE_TO_END: 291 if (currentPosition <= 0f) { 292 return 0; 293 } 294 return 1; 295 case ON_UP_NEVER_COMPLETE_TO_START: 296 return 1; 297 case ON_UP_STOP: 298 return Float.NaN; 299 case ON_UP_DECELERATE: 300 return Math.max(0, Math.min(1, rest)); 301 case ON_UP_DECELERATE_AND_COMPLETE: // complete if within 20% of edge #todo improve 302 if (rest > 0.2f && rest < 0.8f) { 303 return rest; 304 } else { 305 return rest > .5f ? 1 : 0; 306 } 307 case ON_UP_AUTOCOMPLETE: 308 } 309 310 if (DEBUG) { 311 Utils.log(" currentPosition = " + currentPosition); 312 Utils.log(" velocity = " + velocity); 313 Utils.log(" peek = " + rest); 314 Utils.log("mMaxAcceleration = " + mMaxAcceleration); 315 } 316 return rest > .5 ? 1 : 0; 317 } 318 config(float position, float velocity, long start, float duration)319 void config(float position, float velocity, long start, float duration) { 320 mStart = start; 321 if (Math.abs(velocity) > mMaxVelocity) { 322 velocity = mMaxVelocity * Math.signum(velocity); 323 } 324 mDestination = getDestinationPosition(position, velocity, duration); 325 if (mDestination == position) { 326 mEngine = null; 327 return; 328 } 329 if ((mOnTouchUp == ON_UP_DECELERATE) 330 && (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY)) { 331 StopLogicEngine.Decelerate sld; 332 if (mEngine instanceof StopLogicEngine.Decelerate) { 333 sld = (StopLogicEngine.Decelerate) mEngine; 334 } else { 335 mEngine = sld = new StopLogicEngine.Decelerate(); 336 } 337 sld.config(position, mDestination, velocity); 338 return; 339 } 340 341 342 if (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY) { 343 StopLogicEngine sl; 344 if (mEngine instanceof StopLogicEngine) { 345 sl = (StopLogicEngine) mEngine; 346 } else { 347 mEngine = sl = new StopLogicEngine(); 348 } 349 350 sl.config(position, mDestination, velocity, 351 duration, mMaxAcceleration, 352 mMaxVelocity); 353 return; 354 } 355 SpringStopEngine sl; 356 if (mEngine instanceof SpringStopEngine) { 357 sl = (SpringStopEngine) mEngine; 358 } else { 359 mEngine = sl = new SpringStopEngine(); 360 } 361 362 sl.springConfig(position, mDestination, velocity, 363 mSpringMass, 364 mSpringStiffness, 365 mSpringDamping, 366 mSpringStopThreshold, mSpringBoundary); 367 } 368 369 /** 370 * @param currentTime time in nanoseconds 371 * @return new values of progress 372 */ getTouchUpProgress(long currentTime)373 public float getTouchUpProgress(long currentTime) { 374 float time = (currentTime - mStart) * 1E-9f; 375 float pos = mEngine.getInterpolation(time); 376 if (mEngine.isStopped()) { 377 pos = mDestination; 378 } 379 return pos; 380 } 381 printInfo()382 public void printInfo() { 383 if (mAutoCompleteMode == MODE_CONTINUOUS_VELOCITY) { 384 System.out.println("velocity = " + mEngine.getVelocity()); 385 System.out.println("mMaxAcceleration = " + mMaxAcceleration); 386 System.out.println("mMaxVelocity = " + mMaxVelocity); 387 } else { 388 System.out.println("mSpringMass = " + mSpringMass); 389 System.out.println("mSpringStiffness = " + mSpringStiffness); 390 System.out.println("mSpringDamping = " + mSpringDamping); 391 System.out.println("mSpringStopThreshold = " + mSpringStopThreshold); 392 System.out.println("mSpringBoundary = " + mSpringBoundary); 393 } 394 } 395 isNotDone(float progress)396 public boolean isNotDone(float progress) { 397 if (mOnTouchUp == ON_UP_STOP) { 398 return false; 399 } 400 return mEngine != null && !mEngine.isStopped(); 401 } 402 } 403 404 /** 405 * For the given position (in the MotionLayout coordinate space) determine whether we accept 406 * the first down for on swipe. 407 * <p> 408 * This is based off {@link OnSwipe#mLimitBoundsTo}. If null, we accept the drag at any 409 * position, otherwise, we only accept it if it's within its bounds. 410 */ 411 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) isFirstDownAccepted(float posX, float posY)412 public boolean isFirstDownAccepted(float posX, float posY) { 413 if (mOnSwipe == null) { 414 return false; 415 } 416 417 if (mOnSwipe.mLimitBoundsTo != null) { 418 WidgetState targetWidget = mState.get(mOnSwipe.mLimitBoundsTo); 419 if (targetWidget == null) { 420 System.err.println("mLimitBoundsTo target is null"); 421 return false; 422 } 423 // Calculate against the interpolated/current frame 424 WidgetFrame frame = targetWidget.getFrame(2); 425 return posX >= frame.left && posX < frame.right && posY >= frame.top 426 && posY < frame.bottom; 427 } else { 428 return true; 429 } 430 } 431 432 /** 433 * Converts from xy drag to progress 434 * This should be used till touch up 435 * 436 * @param currentProgress 0...1 progress in 437 * @param baseW parent width 438 * @param baseH parent height 439 * @param dx change in x 440 * @param dy change in y 441 * @return the change in progress 442 */ dragToProgress(float currentProgress, int baseW, int baseH, float dx, float dy)443 public float dragToProgress(float currentProgress, int baseW, int baseH, float dx, float dy) { 444 Collection<WidgetState> widgets = mState.values(); 445 WidgetState childWidget = null; 446 for (WidgetState widget : widgets) { 447 childWidget = widget; 448 break; 449 } 450 if (mOnSwipe == null || childWidget == null) { 451 if (childWidget != null) { 452 return -dy / childWidget.mParentHeight; 453 } 454 return 1.0f; 455 } 456 if (mOnSwipe.mAnchorId == null) { 457 458 float[] dir = mOnSwipe.getDirection(); 459 float motionDpDtX = childWidget.mParentHeight; 460 float motionDpDtY = childWidget.mParentHeight; 461 462 float drag = (dir[0] != 0) ? dx * Math.abs(dir[0]) / motionDpDtX 463 : dy * Math.abs(dir[1]) / motionDpDtY; 464 return drag * mOnSwipe.getScale(); 465 } 466 WidgetState base = mState.get(mOnSwipe.mAnchorId); 467 float[] dir = mOnSwipe.getDirection(); 468 float[] side = mOnSwipe.getSide(); 469 float[] motionDpDt = new float[2]; 470 471 base.interpolate(baseW, baseH, currentProgress, this); 472 base.mMotionControl.getDpDt(currentProgress, side[0], side[1], motionDpDt); 473 float drag = (dir[0] != 0) ? dx * Math.abs(dir[0]) / motionDpDt[0] 474 : dy * Math.abs(dir[1]) / motionDpDt[1]; 475 if (DEBUG) { 476 Utils.log(" drag " + drag); 477 } 478 return drag * mOnSwipe.getScale(); 479 } 480 481 /** 482 * Set the start of the touch up 483 * 484 * @param currentProgress 0...1 progress in 485 * @param currentTime time in nanoseconds 486 * @param velocityX pixels per millisecond 487 * @param velocityY pixels per millisecond 488 */ setTouchUp(float currentProgress, long currentTime, float velocityX, float velocityY)489 public void setTouchUp(float currentProgress, 490 long currentTime, 491 float velocityX, 492 float velocityY) { 493 if (mOnSwipe != null) { 494 if (DEBUG) { 495 Utils.log(" >>> velocity x,y = " + velocityX + " , " + velocityY); 496 } 497 WidgetState base = mState.get(mOnSwipe.mAnchorId); 498 float[] motionDpDt = new float[2]; 499 float[] dir = mOnSwipe.getDirection(); 500 float[] side = mOnSwipe.getSide(); 501 base.mMotionControl.getDpDt(currentProgress, side[0], side[1], motionDpDt); 502 float movementInDir = dir[0] * motionDpDt[0] + dir[1] * motionDpDt[1]; 503 if (Math.abs(movementInDir) < 0.01) { 504 if (DEBUG) { 505 Utils.log(" >>> cap minimum v!! "); 506 } 507 motionDpDt[0] = .01f; 508 motionDpDt[1] = .01f; 509 } 510 511 float drag = (dir[0] != 0) ? velocityX / motionDpDt[0] : velocityY / motionDpDt[1]; 512 drag *= mOnSwipe.getScale(); 513 if (DEBUG) { 514 Utils.log(" >>> velocity " + drag); 515 Utils.log(" >>> mDuration " + mDuration); 516 Utils.log(" >>> currentProgress " + currentProgress); 517 } 518 mOnSwipe.config(currentProgress, drag, currentTime, mDuration * 1E-3f); 519 if (DEBUG) { 520 mOnSwipe.printInfo(); 521 } 522 } 523 } 524 525 /** 526 * get the current touch up progress current time in nanoseconds 527 * (ideally coming from an animation clock) 528 * 529 * @param currentTime in nanoseconds 530 * @return progress 531 */ getTouchUpProgress(long currentTime)532 public float getTouchUpProgress(long currentTime) { 533 if (mOnSwipe != null) { 534 return mOnSwipe.getTouchUpProgress(currentTime); 535 } 536 return 0; 537 } 538 539 /** 540 * Are we still animating 541 * 542 * @param currentProgress motion progress 543 * @return true to continue moving 544 */ isTouchNotDone(float currentProgress)545 public boolean isTouchNotDone(float currentProgress) { 546 return mOnSwipe.isNotDone(currentProgress); 547 } 548 549 /** 550 * get the interpolater based on a constant or a string 551 */ getInterpolator(int interpolator, String interpolatorString)552 public static Interpolator getInterpolator(int interpolator, String interpolatorString) { 553 switch (interpolator) { 554 case SPLINE_STRING: 555 return v -> (float) Easing.getInterpolator(interpolatorString).get(v); 556 case EASE_IN_OUT: 557 return v -> (float) Easing.getInterpolator("standard").get(v); 558 case EASE_IN: 559 return v -> (float) Easing.getInterpolator("accelerate").get(v); 560 case EASE_OUT: 561 return v -> (float) Easing.getInterpolator("decelerate").get(v); 562 case LINEAR: 563 return v -> (float) Easing.getInterpolator("linear").get(v); 564 case ANTICIPATE: 565 return v -> (float) Easing.getInterpolator("anticipate").get(v); 566 case OVERSHOOT: 567 return v -> (float) Easing.getInterpolator("overshoot").get(v); 568 case BOUNCE: // TODO make a better bounce 569 return v -> (float) Easing.getInterpolator("spline(0.0, 0.2, 0.4, 0.6, " 570 + "0.8 ,1.0, 0.8, 1.0, 0.9, 1.0)").get(v); 571 } 572 return null; 573 } 574 575 // @TODO: add description 576 @SuppressWarnings("HiddenTypeParameter") findPreviousPosition(String target, int frameNumber)577 public KeyPosition findPreviousPosition(String target, int frameNumber) { 578 while (frameNumber >= 0) { 579 HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber); 580 if (map != null) { 581 KeyPosition keyPosition = map.get(target); 582 if (keyPosition != null) { 583 return keyPosition; 584 } 585 } 586 frameNumber--; 587 } 588 return null; 589 } 590 591 // @TODO: add description 592 @SuppressWarnings("HiddenTypeParameter") findNextPosition(String target, int frameNumber)593 public KeyPosition findNextPosition(String target, int frameNumber) { 594 while (frameNumber <= 100) { 595 HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber); 596 if (map != null) { 597 KeyPosition keyPosition = map.get(target); 598 if (keyPosition != null) { 599 return keyPosition; 600 } 601 } 602 frameNumber++; 603 } 604 return null; 605 } 606 607 // @TODO: add description getNumberKeyPositions(WidgetFrame frame)608 public int getNumberKeyPositions(WidgetFrame frame) { 609 int numKeyPositions = 0; 610 int frameNumber = 0; 611 while (frameNumber <= 100) { 612 HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber); 613 if (map != null) { 614 KeyPosition keyPosition = map.get(frame.widget.stringId); 615 if (keyPosition != null) { 616 numKeyPositions++; 617 } 618 } 619 frameNumber++; 620 } 621 return numKeyPositions; 622 } 623 624 // @TODO: add description getMotion(String id)625 public Motion getMotion(String id) { 626 return getWidgetState(id, null, 0).mMotionControl; 627 } 628 629 // @TODO: add description fillKeyPositions(WidgetFrame frame, float[] x, float[] y, float[] pos)630 public void fillKeyPositions(WidgetFrame frame, float[] x, float[] y, float[] pos) { 631 int numKeyPositions = 0; 632 int frameNumber = 0; 633 while (frameNumber <= 100) { 634 HashMap<String, KeyPosition> map = mKeyPositions.get(frameNumber); 635 if (map != null) { 636 KeyPosition keyPosition = map.get(frame.widget.stringId); 637 if (keyPosition != null) { 638 x[numKeyPositions] = keyPosition.mX; 639 y[numKeyPositions] = keyPosition.mY; 640 pos[numKeyPositions] = keyPosition.mFrame; 641 numKeyPositions++; 642 } 643 } 644 frameNumber++; 645 } 646 } 647 648 // @TODO: add description hasPositionKeyframes()649 public boolean hasPositionKeyframes() { 650 return mKeyPositions.size() > 0; 651 } 652 653 // @TODO: add description setTransitionProperties(TypedBundle bundle)654 public void setTransitionProperties(TypedBundle bundle) { 655 bundle.applyDelta(mBundle); 656 bundle.applyDelta(this); 657 } 658 659 @Override setValue(int id, int value)660 public boolean setValue(int id, int value) { 661 return false; 662 } 663 664 @Override setValue(int id, float value)665 public boolean setValue(int id, float value) { 666 if (id == TypedValues.TransitionType.TYPE_STAGGERED) { 667 mStagger = value; 668 } 669 return false; 670 } 671 672 @Override setValue(int id, String value)673 public boolean setValue(int id, String value) { 674 if (id == TransitionType.TYPE_INTERPOLATOR) { 675 mEasing = Easing.getInterpolator(mDefaultInterpolatorString = value); 676 } 677 return false; 678 } 679 680 @Override setValue(int id, boolean value)681 public boolean setValue(int id, boolean value) { 682 return false; 683 } 684 685 @Override getId(String name)686 public int getId(String name) { 687 return 0; 688 } 689 isEmpty()690 public boolean isEmpty() { 691 return mState.isEmpty(); 692 } 693 694 // @TODO: add description clear()695 public void clear() { 696 mState.clear(); 697 } 698 699 /** 700 * Reset animation properties of the Transition. 701 * <p> 702 * This will not affect the internal model of the widgets (a.k.a. {@link #mState}). 703 */ resetProperties()704 void resetProperties() { 705 mOnSwipe = null; 706 mBundle.clear(); 707 } 708 709 // @TODO: add description contains(String key)710 public boolean contains(String key) { 711 return mState.containsKey(key); 712 } 713 714 // @TODO: add description addKeyPosition(String target, TypedBundle bundle)715 public void addKeyPosition(String target, TypedBundle bundle) { 716 getWidgetState(target, null, 0).setKeyPosition(bundle); 717 } 718 719 // @TODO: add description addKeyAttribute(String target, TypedBundle bundle)720 public void addKeyAttribute(String target, TypedBundle bundle) { 721 getWidgetState(target, null, 0).setKeyAttribute(bundle); 722 } 723 724 /** 725 * Add a key attribute and the custom variables into the 726 * @param target the id of the target 727 * @param bundle the key attributes bundle containing position etc. 728 * @param custom the customVariables to add at that position 729 */ addKeyAttribute(String target, TypedBundle bundle, CustomVariable[]custom)730 public void addKeyAttribute(String target, TypedBundle bundle, CustomVariable[]custom) { 731 getWidgetState(target, null, 0).setKeyAttribute(bundle,custom); 732 } 733 734 // @TODO: add description addKeyCycle(String target, TypedBundle bundle)735 public void addKeyCycle(String target, TypedBundle bundle) { 736 getWidgetState(target, null, 0).setKeyCycle(bundle); 737 } 738 739 // @TODO: add description addKeyPosition(String target, int frame, int type, float x, float y)740 public void addKeyPosition(String target, int frame, int type, float x, float y) { 741 TypedBundle bundle = new TypedBundle(); 742 bundle.add(TypedValues.PositionType.TYPE_POSITION_TYPE, 2); 743 bundle.add(TypedValues.TYPE_FRAME_POSITION, frame); 744 bundle.add(TypedValues.PositionType.TYPE_PERCENT_X, x); 745 bundle.add(TypedValues.PositionType.TYPE_PERCENT_Y, y); 746 getWidgetState(target, null, 0).setKeyPosition(bundle); 747 748 KeyPosition keyPosition = new KeyPosition(target, frame, type, x, y); 749 HashMap<String, KeyPosition> map = mKeyPositions.get(frame); 750 if (map == null) { 751 map = new HashMap<>(); 752 mKeyPositions.put(frame, map); 753 } 754 map.put(target, keyPosition); 755 } 756 757 // @TODO: add description addCustomFloat(int state, String widgetId, String property, float value)758 public void addCustomFloat(int state, String widgetId, String property, float value) { 759 WidgetState widgetState = getWidgetState(widgetId, null, state); 760 WidgetFrame frame = widgetState.getFrame(state); 761 frame.addCustomFloat(property, value); 762 } 763 764 // @TODO: add description addCustomColor(int state, String widgetId, String property, int color)765 public void addCustomColor(int state, String widgetId, String property, int color) { 766 WidgetState widgetState = getWidgetState(widgetId, null, state); 767 WidgetFrame frame = widgetState.getFrame(state); 768 frame.addCustomColor(property, color); 769 } 770 calculateParentDimensions(float progress)771 private void calculateParentDimensions(float progress) { 772 mParentInterpolatedWidth = (int) (0.5f + 773 mParentStartWidth + (mParentEndWidth - mParentStartWidth) * progress); 774 mParentInterpolateHeight = (int) (0.5f + 775 mParentStartHeight + (mParentEndHeight - mParentStartHeight) * progress); 776 } 777 getInterpolatedWidth()778 public int getInterpolatedWidth() { 779 return mParentInterpolatedWidth; 780 } 781 getInterpolatedHeight()782 public int getInterpolatedHeight() { 783 return mParentInterpolateHeight; 784 } 785 /** 786 * Update container of parameters for the state 787 * 788 * @param container contains all the widget parameters 789 * @param state starting or ending 790 */ updateFrom(ConstraintWidgetContainer container, int state)791 public void updateFrom(ConstraintWidgetContainer container, int state) { 792 mWrap = container.mListDimensionBehaviors[0] 793 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 794 mWrap |= container.mListDimensionBehaviors[1] 795 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 796 if (state == START) { 797 mParentInterpolatedWidth = mParentStartWidth = container.getWidth(); 798 mParentInterpolateHeight = mParentStartHeight = container.getHeight(); 799 } else { 800 mParentEndWidth = container.getWidth(); 801 mParentEndHeight = container.getHeight(); 802 } 803 final ArrayList<ConstraintWidget> children = container.getChildren(); 804 final int count = children.size(); 805 WidgetState[] states = new WidgetState[count]; 806 807 for (int i = 0; i < count; i++) { 808 ConstraintWidget child = children.get(i); 809 WidgetState widgetState = getWidgetState(child.stringId, null, state); 810 states[i] = widgetState; 811 widgetState.update(child, state); 812 String id = widgetState.getPathRelativeId(); 813 if (id != null) { 814 widgetState.setPathRelative(getWidgetState(id, null, state)); 815 } 816 } 817 818 calcStagger(); 819 } 820 821 // @TODO: add description interpolate(int parentWidth, int parentHeight, float progress)822 public void interpolate(int parentWidth, int parentHeight, float progress) { 823 if (mWrap) { 824 calculateParentDimensions(progress); 825 } 826 827 if (mEasing != null) { 828 progress = (float) mEasing.get(progress); 829 } 830 for (String key : mState.keySet()) { 831 WidgetState widget = mState.get(key); 832 widget.interpolate(parentWidth, parentHeight, progress, this); 833 } 834 } 835 836 // @TODO: add description getStart(String id)837 public WidgetFrame getStart(String id) { 838 WidgetState widgetState = mState.get(id); 839 if (widgetState == null) { 840 return null; 841 } 842 return widgetState.mStart; 843 } 844 845 // @TODO: add description getEnd(String id)846 public WidgetFrame getEnd(String id) { 847 WidgetState widgetState = mState.get(id); 848 if (widgetState == null) { 849 return null; 850 } 851 return widgetState.mEnd; 852 } 853 854 // @TODO: add description getInterpolated(String id)855 public WidgetFrame getInterpolated(String id) { 856 WidgetState widgetState = mState.get(id); 857 if (widgetState == null) { 858 return null; 859 } 860 return widgetState.mInterpolated; 861 } 862 863 // @TODO: add description getPath(String id)864 public float[] getPath(String id) { 865 WidgetState widgetState = mState.get(id); 866 int duration = 1000; 867 int frames = duration / 16; 868 float[] mPoints = new float[frames * 2]; 869 widgetState.mMotionControl.buildPath(mPoints, frames); 870 return mPoints; 871 } 872 873 // @TODO: add description getKeyFrames(String id, float[] rectangles, int[] pathMode, int[] position)874 public int getKeyFrames(String id, float[] rectangles, int[] pathMode, int[] position) { 875 WidgetState widgetState = mState.get(id); 876 return widgetState.mMotionControl.buildKeyFrames(rectangles, pathMode, position); 877 } 878 879 @SuppressWarnings("unused") getWidgetState(String widgetId)880 private WidgetState getWidgetState(String widgetId) { 881 return this.mState.get(widgetId); 882 } 883 getWidgetState(String widgetId, ConstraintWidget child, int transitionState)884 public WidgetState getWidgetState(String widgetId, 885 ConstraintWidget child, 886 int transitionState) { 887 WidgetState widgetState = this.mState.get(widgetId); 888 if (widgetState == null) { 889 widgetState = new WidgetState(); 890 mBundle.applyDelta(widgetState.mMotionControl); 891 widgetState.mMotionWidgetStart.updateMotion(widgetState.mMotionControl); 892 mState.put(widgetId, widgetState); 893 if (child != null) { 894 widgetState.update(child, transitionState); 895 } 896 } 897 return widgetState; 898 } 899 900 /** 901 * Used in debug draw 902 */ getStart(ConstraintWidget child)903 public WidgetFrame getStart(ConstraintWidget child) { 904 return getWidgetState(child.stringId, null, Transition.START).mStart; 905 } 906 907 /** 908 * Used in debug draw 909 */ getEnd(ConstraintWidget child)910 public WidgetFrame getEnd(ConstraintWidget child) { 911 return getWidgetState(child.stringId, null, Transition.END).mEnd; 912 } 913 914 /** 915 * Used after the interpolation 916 */ getInterpolated(ConstraintWidget child)917 public WidgetFrame getInterpolated(ConstraintWidget child) { 918 return getWidgetState(child.stringId, null, Transition.INTERPOLATED).mInterpolated; 919 } 920 921 /** 922 * This gets the interpolator being used 923 */ getInterpolator()924 public Interpolator getInterpolator() { 925 return getInterpolator(mDefaultInterpolator, mDefaultInterpolatorString); 926 } 927 928 /** 929 * This gets the auto transition mode being used 930 */ getAutoTransition()931 public int getAutoTransition() { 932 return mAutoTransition; 933 } 934 935 public static class WidgetState { 936 WidgetFrame mStart; 937 WidgetFrame mEnd; 938 WidgetFrame mInterpolated; 939 Motion mMotionControl; 940 boolean mNeedSetup = true; 941 MotionWidget mMotionWidgetStart; 942 MotionWidget mMotionWidgetEnd; 943 MotionWidget mMotionWidgetInterpolated; 944 KeyCache mKeyCache = new KeyCache(); 945 int mParentHeight = -1; 946 int mParentWidth = -1; 947 WidgetState()948 public WidgetState() { 949 mStart = new WidgetFrame(); 950 mEnd = new WidgetFrame(); 951 mInterpolated = new WidgetFrame(); 952 mMotionWidgetStart = new MotionWidget(mStart); 953 mMotionWidgetEnd = new MotionWidget(mEnd); 954 mMotionWidgetInterpolated = new MotionWidget(mInterpolated); 955 mMotionControl = new Motion(mMotionWidgetStart); 956 mMotionControl.setStart(mMotionWidgetStart); 957 mMotionControl.setEnd(mMotionWidgetEnd); 958 } 959 setKeyPosition(TypedBundle prop)960 public void setKeyPosition(TypedBundle prop) { 961 MotionKeyPosition keyPosition = new MotionKeyPosition(); 962 prop.applyDelta(keyPosition); 963 mMotionControl.addKey(keyPosition); 964 } 965 setKeyAttribute(TypedBundle prop)966 public void setKeyAttribute(TypedBundle prop) { 967 MotionKeyAttributes keyAttributes = new MotionKeyAttributes(); 968 prop.applyDelta(keyAttributes); 969 mMotionControl.addKey(keyAttributes); 970 } 971 972 /** 973 * Set tge keyAttribute bundle and associated custom attributes 974 * @param prop 975 * @param custom 976 */ setKeyAttribute(TypedBundle prop, CustomVariable[] custom)977 public void setKeyAttribute(TypedBundle prop, CustomVariable[] custom) { 978 MotionKeyAttributes keyAttributes = new MotionKeyAttributes(); 979 prop.applyDelta(keyAttributes); 980 if (custom != null) { 981 for (int i = 0; i < custom.length; i++) { 982 keyAttributes.mCustom.put( custom[i].getName(), custom[i]); 983 } 984 } 985 mMotionControl.addKey(keyAttributes); 986 } 987 setKeyCycle(TypedBundle prop)988 public void setKeyCycle(TypedBundle prop) { 989 MotionKeyCycle keyAttributes = new MotionKeyCycle(); 990 prop.applyDelta(keyAttributes); 991 mMotionControl.addKey(keyAttributes); 992 } 993 update(ConstraintWidget child, int state)994 public void update(ConstraintWidget child, int state) { 995 if (state == START) { 996 mStart.update(child); 997 mMotionWidgetStart.updateMotion(mMotionWidgetStart); 998 mMotionControl.setStart(mMotionWidgetStart); 999 mNeedSetup = true; 1000 } else if (state == END) { 1001 mEnd.update(child); 1002 mMotionControl.setEnd(mMotionWidgetEnd); 1003 mNeedSetup = true; 1004 } 1005 mParentWidth = -1; 1006 } 1007 1008 /** 1009 * Return the id of the widget to animate relative to 1010 * 1011 * @return id of widget or null 1012 */ getPathRelativeId()1013 String getPathRelativeId() { 1014 return mMotionControl.getAnimateRelativeTo(); 1015 } 1016 getFrame(int type)1017 public WidgetFrame getFrame(int type) { 1018 if (type == START) { 1019 return mStart; 1020 } else if (type == END) { 1021 return mEnd; 1022 } 1023 return mInterpolated; 1024 } 1025 interpolate(int parentWidth, int parentHeight, float progress, Transition transition)1026 public void interpolate(int parentWidth, 1027 int parentHeight, 1028 float progress, 1029 Transition transition) { 1030 // TODO only update if parentHeight != mParentHeight || parentWidth != mParentWidth) { 1031 mParentHeight = parentHeight; 1032 mParentWidth = parentWidth; 1033 if (mNeedSetup) { 1034 mMotionControl.setup(parentWidth, parentHeight, 1, System.nanoTime()); 1035 mNeedSetup = false; 1036 } 1037 WidgetFrame.interpolate(parentWidth, parentHeight, 1038 mInterpolated, mStart, mEnd, transition, progress); 1039 mInterpolated.interpolatedPos = progress; 1040 mMotionControl.interpolate(mMotionWidgetInterpolated, 1041 progress, System.nanoTime(), mKeyCache); 1042 } 1043 setPathRelative(WidgetState widgetState)1044 public void setPathRelative(WidgetState widgetState) { 1045 mMotionControl.setupRelative(widgetState.mMotionControl); 1046 } 1047 } 1048 1049 static class KeyPosition { 1050 int mFrame; 1051 String mTarget; 1052 int mType; 1053 float mX; 1054 float mY; 1055 KeyPosition(String target, int frame, int type, float x, float y)1056 KeyPosition(String target, int frame, int type, float x, float y) { 1057 this.mTarget = target; 1058 this.mFrame = frame; 1059 this.mType = type; 1060 this.mX = x; 1061 this.mY = y; 1062 } 1063 } 1064 calcStagger()1065 public void calcStagger() { 1066 if (mStagger == 0.0f) { 1067 return; 1068 } 1069 boolean flip = mStagger < 0.0; 1070 1071 float stagger = Math.abs(mStagger); 1072 float min = Float.MAX_VALUE, max = -Float.MAX_VALUE; 1073 boolean useMotionStagger = false; 1074 1075 for (String widgetId : mState.keySet()) { 1076 WidgetState widgetState = mState.get(widgetId); 1077 Motion f = widgetState.mMotionControl; 1078 if (!Float.isNaN(f.getMotionStagger())) { 1079 useMotionStagger = true; 1080 break; 1081 } 1082 } 1083 if (useMotionStagger) { 1084 for (String widgetId : mState.keySet()) { 1085 WidgetState widgetState = mState.get(widgetId); 1086 Motion f = widgetState.mMotionControl; 1087 float widgetStagger = f.getMotionStagger(); 1088 if (!Float.isNaN(widgetStagger)) { 1089 min = Math.min(min, widgetStagger); 1090 max = Math.max(max, widgetStagger); 1091 } 1092 } 1093 1094 for (String widgetId : mState.keySet()) { 1095 WidgetState widgetState = mState.get(widgetId); 1096 Motion f = widgetState.mMotionControl; 1097 1098 float widgetStagger = f.getMotionStagger(); 1099 if (!Float.isNaN(widgetStagger)) { 1100 float scale = 1 / (1 - stagger); 1101 1102 float offset = stagger - stagger * (widgetStagger - min) / (max - min); 1103 if (flip) { 1104 offset = stagger - stagger 1105 * ((max - widgetStagger) / (max - min)); 1106 } 1107 f.setStaggerScale(scale); 1108 f.setStaggerOffset(offset); 1109 } 1110 } 1111 1112 } else { 1113 for (String widgetId : mState.keySet()) { 1114 WidgetState widgetState = mState.get(widgetId); 1115 Motion f = widgetState.mMotionControl; 1116 float x = f.getFinalX(); 1117 float y = f.getFinalY(); 1118 float widgetStagger = x + y; 1119 min = Math.min(min, widgetStagger); 1120 max = Math.max(max, widgetStagger); 1121 } 1122 1123 for (String widgetId : mState.keySet()) { 1124 WidgetState widgetState = mState.get(widgetId); 1125 Motion f = widgetState.mMotionControl; 1126 float x = f.getFinalX(); 1127 float y = f.getFinalY(); 1128 float widgetStagger = x + y; 1129 float offset = stagger - stagger * (widgetStagger - min) / (max - min); 1130 if (flip) { 1131 offset = stagger - stagger 1132 * ((max - widgetStagger) / (max - min)); 1133 } 1134 1135 float scale = 1 / (1 - stagger); 1136 f.setStaggerScale(scale); 1137 f.setStaggerOffset(offset); 1138 } 1139 } 1140 } 1141 } 1142