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.MotionKeyPosition; 21 import androidx.constraintlayout.core.motion.utils.Easing; 22 import androidx.constraintlayout.core.motion.utils.Utils; 23 24 import java.util.Arrays; 25 import java.util.HashMap; 26 import java.util.Set; 27 28 /** 29 * This is used to capture and play back path of the layout. 30 * It is used to set the bounds of the view (view.layout(l, t, r, b)) 31 */ 32 public class MotionPaths implements Comparable<MotionPaths> { 33 public static final String TAG = "MotionPaths"; 34 public static final boolean DEBUG = false; 35 public static final boolean OLD_WAY = false; // the computes the positions the old way 36 static final int OFF_POSITION = 0; 37 static final int OFF_X = 1; 38 static final int OFF_Y = 2; 39 static final int OFF_WIDTH = 3; 40 static final int OFF_HEIGHT = 4; 41 static final int OFF_PATH_ROTATE = 5; 42 43 // mode and type have same numbering scheme 44 public static final int PERPENDICULAR = MotionKeyPosition.TYPE_PATH; 45 public static final int CARTESIAN = MotionKeyPosition.TYPE_CARTESIAN; 46 public static final int SCREEN = MotionKeyPosition.TYPE_SCREEN; 47 static String[] sNames = {"position", "x", "y", "width", "height", "pathRotate"}; 48 public String mId; 49 Easing mKeyFrameEasing; 50 int mDrawPath = 0; 51 float mTime; 52 float mPosition; 53 float mX; 54 float mY; 55 float mWidth; 56 float mHeight; 57 float mPathRotate = Float.NaN; 58 float mProgress = Float.NaN; 59 int mPathMotionArc = UNSET; 60 String mAnimateRelativeTo = null; 61 float mRelativeAngle = Float.NaN; 62 Motion mRelativeToController = null; 63 64 HashMap<String, CustomVariable> mCustomAttributes = new HashMap<>(); 65 int mMode = 0; // how was this point computed 1=perpendicular 2=deltaRelative 66 int mAnimateCircleAngleTo; // since angles loop there are 4 ways we can pic direction 67 MotionPaths()68 public MotionPaths() { 69 } 70 71 /** 72 * set up with Cartesian 73 */ initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)74 void initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) { 75 float position = c.mFramePosition / 100f; 76 MotionPaths point = this; 77 point.mTime = position; 78 79 mDrawPath = c.mDrawPath; 80 float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth; 81 float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight; 82 float scaleX = endTimePoint.mWidth - startTimePoint.mWidth; 83 float scaleY = endTimePoint.mHeight - startTimePoint.mHeight; 84 85 point.mPosition = point.mTime; 86 87 float path = position; // the position on the path 88 89 float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2; 90 float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2; 91 float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2; 92 float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2; 93 float pathVectorX = endCenterX - startCenterX; 94 float pathVectorY = endCenterY - startCenterY; 95 point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2); 96 point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2); 97 point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth); 98 point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight); 99 100 float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX; 101 float dydx = Float.isNaN(c.mAltPercentY) ? 0 : c.mAltPercentY; 102 float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY; 103 float dxdy = Float.isNaN(c.mAltPercentX) ? 0 : c.mAltPercentX; 104 point.mMode = MotionPaths.CARTESIAN; 105 point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx 106 + pathVectorY * dxdy - scaleX * scaleWidth / 2); 107 point.mY = (int) (startTimePoint.mY + pathVectorX * dydx 108 + pathVectorY * dydy - scaleY * scaleHeight / 2); 109 110 point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing); 111 point.mPathMotionArc = c.mPathMotionArc; 112 } 113 114 /** 115 * takes the new keyPosition 116 */ MotionPaths(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)117 public MotionPaths(int parentWidth, 118 int parentHeight, 119 MotionKeyPosition c, 120 MotionPaths startTimePoint, 121 MotionPaths endTimePoint) { 122 if (startTimePoint.mAnimateRelativeTo != null) { 123 initPolar(parentWidth, parentHeight, c, startTimePoint, endTimePoint); 124 return; 125 } 126 switch (c.mPositionType) { 127 case MotionKeyPosition.TYPE_SCREEN: 128 initScreen(parentWidth, parentHeight, c, startTimePoint, endTimePoint); 129 return; 130 case MotionKeyPosition.TYPE_PATH: 131 initPath(c, startTimePoint, endTimePoint); 132 return; 133 default: 134 case MotionKeyPosition.TYPE_CARTESIAN: 135 initCartesian(c, startTimePoint, endTimePoint); 136 return; 137 } 138 } 139 initPolar(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths s, MotionPaths e)140 void initPolar(int parentWidth, 141 int parentHeight, 142 MotionKeyPosition c, 143 MotionPaths s, 144 MotionPaths e) { 145 float position = c.mFramePosition / 100f; 146 this.mTime = position; 147 mDrawPath = c.mDrawPath; 148 this.mMode = c.mPositionType; // mode and type have same numbering scheme 149 float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth; 150 float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight; 151 float scaleX = e.mWidth - s.mWidth; 152 float scaleY = e.mHeight - s.mHeight; 153 this.mPosition = this.mTime; 154 mWidth = (int) (s.mWidth + scaleX * scaleWidth); 155 mHeight = (int) (s.mHeight + scaleY * scaleHeight); 156 @SuppressWarnings("unused") float startfactor = 1 - position; 157 @SuppressWarnings("unused") float endfactor = position; 158 159 switch (c.mPositionType) { 160 case MotionKeyPosition.TYPE_SCREEN: 161 this.mX = Float.isNaN(c.mPercentX) ? (position * (e.mX - s.mX) + s.mX) 162 : c.mPercentX * Math.min(scaleHeight, scaleWidth); 163 this.mY = Float.isNaN(c.mPercentY) 164 ? (position * (e.mY - s.mY) + s.mY) : c.mPercentY; 165 break; 166 167 case MotionKeyPosition.TYPE_PATH: 168 this.mX = (Float.isNaN(c.mPercentX) 169 ? position : c.mPercentX) * (e.mX - s.mX) + s.mX; 170 this.mY = (Float.isNaN(c.mPercentY) 171 ? position : c.mPercentY) * (e.mY - s.mY) + s.mY; 172 break; 173 default: 174 case MotionKeyPosition.TYPE_CARTESIAN: 175 this.mX = (Float.isNaN(c.mPercentX) 176 ? position : c.mPercentX) * (e.mX - s.mX) + s.mX; 177 this.mY = (Float.isNaN(c.mPercentY) 178 ? position : c.mPercentY) * (e.mY - s.mY) + s.mY; 179 break; 180 } 181 182 this.mAnimateRelativeTo = s.mAnimateRelativeTo; 183 this.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing); 184 this.mPathMotionArc = c.mPathMotionArc; 185 } 186 187 // @TODO: add description setupRelative(Motion mc, MotionPaths relative)188 public void setupRelative(Motion mc, MotionPaths relative) { 189 double dx = mX + mWidth / 2 - relative.mX - relative.mWidth / 2; 190 double dy = mY + mHeight / 2 - relative.mY - relative.mHeight / 2; 191 mRelativeToController = mc; 192 193 mX = (float) Math.hypot(dy, dx); 194 if (Float.isNaN(mRelativeAngle)) { 195 mY = (float) (Math.atan2(dy, dx) + Math.PI / 2); 196 } else { 197 mY = (float) Math.toRadians(mRelativeAngle); 198 } 199 } 200 initScreen(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)201 void initScreen(int parentWidth, 202 int parentHeight, 203 MotionKeyPosition c, 204 MotionPaths startTimePoint, 205 MotionPaths endTimePoint) { 206 float position = c.mFramePosition / 100f; 207 MotionPaths point = this; 208 point.mTime = position; 209 210 mDrawPath = c.mDrawPath; 211 float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth; 212 float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight; 213 214 float scaleX = endTimePoint.mWidth - startTimePoint.mWidth; 215 float scaleY = endTimePoint.mHeight - startTimePoint.mHeight; 216 217 point.mPosition = point.mTime; 218 219 float path = position; // the position on the path 220 221 float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2; 222 float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2; 223 float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2; 224 float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2; 225 float pathVectorX = endCenterX - startCenterX; 226 float pathVectorY = endCenterY - startCenterY; 227 point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2); 228 point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2); 229 point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth); 230 point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight); 231 232 point.mMode = MotionPaths.SCREEN; 233 if (!Float.isNaN(c.mPercentX)) { 234 parentWidth -= (int) point.mWidth; 235 point.mX = (int) (c.mPercentX * parentWidth); 236 } 237 if (!Float.isNaN(c.mPercentY)) { 238 parentHeight -= (int) point.mHeight; 239 point.mY = (int) (c.mPercentY * parentHeight); 240 } 241 242 point.mAnimateRelativeTo = mAnimateRelativeTo; 243 point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing); 244 point.mPathMotionArc = c.mPathMotionArc; 245 } 246 initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)247 void initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) { 248 249 float position = c.mFramePosition / 100f; 250 MotionPaths point = this; 251 point.mTime = position; 252 253 mDrawPath = c.mDrawPath; 254 float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth; 255 float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight; 256 257 float scaleX = endTimePoint.mWidth - startTimePoint.mWidth; 258 float scaleY = endTimePoint.mHeight - startTimePoint.mHeight; 259 260 point.mPosition = point.mTime; 261 262 float path = Float.isNaN(c.mPercentX) ? position : c.mPercentX; // the position on the path 263 264 float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2; 265 float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2; 266 float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2; 267 float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2; 268 float pathVectorX = endCenterX - startCenterX; 269 float pathVectorY = endCenterY - startCenterY; 270 point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2); 271 point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2); 272 point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth); 273 point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight); 274 float perpendicular = Float.isNaN(c.mPercentY) 275 ? 0 : c.mPercentY; // the position on the path 276 float perpendicularX = -pathVectorY; 277 float perpendicularY = pathVectorX; 278 279 float normalX = perpendicularX * perpendicular; 280 float normalY = perpendicularY * perpendicular; 281 point.mMode = MotionPaths.PERPENDICULAR; 282 point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2); 283 point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2); 284 point.mX += normalX; 285 point.mY += normalY; 286 287 point.mAnimateRelativeTo = mAnimateRelativeTo; 288 point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing); 289 point.mPathMotionArc = c.mPathMotionArc; 290 } 291 xRotate(float sin, float cos, float cx, float cy, float x, float y)292 private static float xRotate(float sin, float cos, float cx, float cy, float x, float y) { 293 x = x - cx; 294 y = y - cy; 295 return x * cos - y * sin + cx; 296 } 297 yRotate(float sin, float cos, float cx, float cy, float x, float y)298 private static float yRotate(float sin, float cos, float cx, float cy, float x, float y) { 299 x = x - cx; 300 y = y - cy; 301 return x * sin + y * cos + cy; 302 } 303 diff(float a, float b)304 private boolean diff(float a, float b) { 305 if (Float.isNaN(a) || Float.isNaN(b)) { 306 return Float.isNaN(a) != Float.isNaN(b); 307 } 308 return Math.abs(a - b) > 0.000001f; 309 } 310 different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode)311 void different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode) { 312 int c = 0; 313 boolean diffx = diff(mX, points.mX); 314 boolean diffy = diff(mY, points.mY); 315 mask[c++] |= diff(mPosition, points.mPosition); 316 mask[c++] |= diffx || diffy || arcMode; 317 mask[c++] |= diffx || diffy || arcMode; 318 mask[c++] |= diff(mWidth, points.mWidth); 319 mask[c++] |= diff(mHeight, points.mHeight); 320 321 } 322 getCenter(double p, int[] toUse, double[] data, float[] point, int offset)323 void getCenter(double p, int[] toUse, double[] data, float[] point, int offset) { 324 float v_x = mX; 325 float v_y = mY; 326 float v_width = mWidth; 327 float v_height = mHeight; 328 float translationX = 0, translationY = 0; 329 for (int i = 0; i < toUse.length; i++) { 330 float value = (float) data[i]; 331 332 switch (toUse[i]) { 333 case OFF_X: 334 v_x = value; 335 break; 336 case OFF_Y: 337 v_y = value; 338 break; 339 case OFF_WIDTH: 340 v_width = value; 341 break; 342 case OFF_HEIGHT: 343 v_height = value; 344 break; 345 } 346 } 347 if (mRelativeToController != null) { 348 float[] pos = new float[2]; 349 float[] vel = new float[2]; 350 351 mRelativeToController.getCenter(p, pos, vel); 352 float rx = pos[0]; 353 float ry = pos[1]; 354 float radius = v_x; 355 float angle = v_y; 356 // TODO Debug angle 357 v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2); 358 v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2); 359 } 360 361 point[offset] = v_x + v_width / 2 + translationX; 362 point[offset + 1] = v_y + v_height / 2 + translationY; 363 } 364 getCenter(double p, int[] toUse, double[] data, float[] point, double[] vdata, float[] velocity)365 void getCenter(double p, 366 int[] toUse, 367 double[] data, 368 float[] point, 369 double[] vdata, 370 float[] velocity) { 371 float v_x = mX; 372 float v_y = mY; 373 float v_width = mWidth; 374 float v_height = mHeight; 375 float dv_x = 0; 376 float dv_y = 0; 377 float dv_width = 0; 378 float dv_height = 0; 379 380 float translationX = 0, translationY = 0; 381 for (int i = 0; i < toUse.length; i++) { 382 float value = (float) data[i]; 383 float dvalue = (float) vdata[i]; 384 385 switch (toUse[i]) { 386 case OFF_X: 387 v_x = value; 388 dv_x = dvalue; 389 break; 390 case OFF_Y: 391 v_y = value; 392 dv_y = dvalue; 393 break; 394 case OFF_WIDTH: 395 v_width = value; 396 dv_width = dvalue; 397 break; 398 case OFF_HEIGHT: 399 v_height = value; 400 dv_height = dvalue; 401 break; 402 } 403 } 404 float dpos_x = dv_x + dv_width / 2; 405 float dpos_y = dv_y + dv_height / 2; 406 407 if (mRelativeToController != null) { 408 float[] pos = new float[2]; 409 float[] vel = new float[2]; 410 mRelativeToController.getCenter(p, pos, vel); 411 float rx = pos[0]; 412 float ry = pos[1]; 413 float radius = v_x; 414 float angle = v_y; 415 float dradius = dv_x; 416 float dangle = dv_y; 417 float drx = vel[0]; 418 float dry = vel[1]; 419 // TODO Debug angle 420 v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2); 421 v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2); 422 dpos_x = (float) (drx + dradius * Math.sin(angle) + Math.cos(angle) * dangle); 423 dpos_y = (float) (dry - dradius * Math.cos(angle) + Math.sin(angle) * dangle); 424 } 425 426 point[0] = v_x + v_width / 2 + translationX; 427 point[1] = v_y + v_height / 2 + translationY; 428 velocity[0] = dpos_x; 429 velocity[1] = dpos_y; 430 } 431 getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset)432 void getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset) { 433 float v_x = mX; 434 float v_y = mY; 435 float v_width = mWidth; 436 float v_height = mHeight; 437 float translationX = 0, translationY = 0; 438 for (int i = 0; i < toUse.length; i++) { 439 float value = (float) data[i]; 440 441 switch (toUse[i]) { 442 case OFF_X: 443 v_x = value; 444 break; 445 case OFF_Y: 446 v_y = value; 447 break; 448 case OFF_WIDTH: 449 v_width = value; 450 break; 451 case OFF_HEIGHT: 452 v_height = value; 453 break; 454 } 455 } 456 if (mRelativeToController != null) { 457 float[] pos = new float[2]; 458 float[] vel = new float[2]; 459 mRelativeToController.getCenter(p, pos, vel); 460 float rx = pos[0]; 461 float ry = pos[1]; 462 float radius = v_x; 463 float angle = v_y; 464 // TODO Debug angle 465 v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2); 466 v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2); 467 } 468 469 point[offset] = v_x + v_width / 2 + translationX; 470 point[offset + 1] = v_y + v_height / 2 + translationY; 471 } 472 getBounds(int[] toUse, double[] data, float[] point, int offset)473 void getBounds(int[] toUse, double[] data, float[] point, int offset) { 474 @SuppressWarnings("unused") float v_x = mX; 475 @SuppressWarnings("unused") float v_y = mY; 476 float v_width = mWidth; 477 float v_height = mHeight; 478 for (int i = 0; i < toUse.length; i++) { 479 float value = (float) data[i]; 480 481 switch (toUse[i]) { 482 case OFF_X: 483 v_x = value; 484 break; 485 case OFF_Y: 486 v_y = value; 487 break; 488 case OFF_WIDTH: 489 v_width = value; 490 break; 491 case OFF_HEIGHT: 492 v_height = value; 493 break; 494 } 495 } 496 point[offset] = v_width; 497 point[offset + 1] = v_height; 498 } 499 500 double[] mTempValue = new double[18]; 501 double[] mTempDelta = new double[18]; 502 503 // Called on the start Time Point setView(float position, MotionWidget view, int[] toUse, double[] data, double[] slope, double[] cycle)504 void setView(float position, 505 MotionWidget view, 506 int[] toUse, 507 double[] data, 508 double[] slope, 509 double[] cycle) { 510 float v_x = mX; 511 float v_y = mY; 512 float v_width = mWidth; 513 float v_height = mHeight; 514 float dv_x = 0; 515 float dv_y = 0; 516 float dv_width = 0; 517 float dv_height = 0; 518 @SuppressWarnings("unused") float delta_path = 0; 519 float path_rotate = Float.NaN; 520 @SuppressWarnings("unused") String mod; 521 522 if (toUse.length != 0 && mTempValue.length <= toUse[toUse.length - 1]) { 523 int scratch_data_length = toUse[toUse.length - 1] + 1; 524 mTempValue = new double[scratch_data_length]; 525 mTempDelta = new double[scratch_data_length]; 526 } 527 Arrays.fill(mTempValue, Double.NaN); 528 for (int i = 0; i < toUse.length; i++) { 529 mTempValue[toUse[i]] = data[i]; 530 mTempDelta[toUse[i]] = slope[i]; 531 } 532 533 for (int i = 0; i < mTempValue.length; i++) { 534 if (Double.isNaN(mTempValue[i]) && (cycle == null || cycle[i] == 0.0)) { 535 continue; 536 } 537 double deltaCycle = (cycle != null) ? cycle[i] : 0.0; 538 float value = (float) (Double.isNaN(mTempValue[i]) 539 ? deltaCycle : mTempValue[i] + deltaCycle); 540 float dvalue = (float) mTempDelta[i]; 541 542 switch (i) { 543 case OFF_POSITION: 544 delta_path = value; 545 break; 546 case OFF_X: 547 v_x = value; 548 dv_x = dvalue; 549 550 break; 551 case OFF_Y: 552 v_y = value; 553 dv_y = dvalue; 554 break; 555 case OFF_WIDTH: 556 v_width = value; 557 dv_width = dvalue; 558 break; 559 case OFF_HEIGHT: 560 v_height = value; 561 dv_height = dvalue; 562 break; 563 case OFF_PATH_ROTATE: 564 path_rotate = value; 565 break; 566 } 567 } 568 569 if (mRelativeToController != null) { 570 float[] pos = new float[2]; 571 float[] vel = new float[2]; 572 573 mRelativeToController.getCenter(position, pos, vel); 574 float rx = pos[0]; 575 float ry = pos[1]; 576 float radius = v_x; 577 float angle = v_y; 578 float dradius = dv_x; 579 float dangle = dv_y; 580 float drx = vel[0]; 581 float dry = vel[1]; 582 583 // TODO Debug angle 584 float pos_x = (float) (rx + radius * Math.sin(angle) - v_width / 2); 585 float pos_y = (float) (ry - radius * Math.cos(angle) - v_height / 2); 586 float dpos_x = (float) (drx + dradius * Math.sin(angle) 587 + radius * Math.cos(angle) * dangle); 588 float dpos_y = (float) (dry - dradius * Math.cos(angle) 589 + radius * Math.sin(angle) * dangle); 590 dv_x = dpos_x; 591 dv_y = dpos_y; 592 v_x = pos_x; 593 v_y = pos_y; 594 if (slope.length >= 2) { 595 slope[0] = dpos_x; 596 slope[1] = dpos_y; 597 } 598 if (!Float.isNaN(path_rotate)) { 599 float rot = (float) (path_rotate + Math.toDegrees(Math.atan2(dv_y, dv_x))); 600 view.setRotationZ(rot); 601 } 602 603 } else { 604 605 if (!Float.isNaN(path_rotate)) { 606 float rot = 0; 607 float dx = dv_x + dv_width / 2; 608 float dy = dv_y + dv_height / 2; 609 if (DEBUG) { 610 Utils.log(TAG, "dv_x =" + dv_x); 611 Utils.log(TAG, "dv_y =" + dv_y); 612 Utils.log(TAG, "dv_width =" + dv_width); 613 Utils.log(TAG, "dv_height =" + dv_height); 614 } 615 rot += (float) (path_rotate + Math.toDegrees(Math.atan2(dy, dx))); 616 view.setRotationZ(rot); 617 if (DEBUG) { 618 Utils.log(TAG, "Rotated " + rot + " = " + dx + "," + dy); 619 } 620 } 621 } 622 623 // Todo: develop a concept of Float layout in MotionWidget widget.layout(float ...) 624 int l = (int) (0.5f + v_x); 625 int t = (int) (0.5f + v_y); 626 int r = (int) (0.5f + v_x + v_width); 627 int b = (int) (0.5f + v_y + v_height); 628 int i_width = r - l; 629 int i_height = b - t; 630 if (OLD_WAY) { // This way may produce more stable with and height but risk gaps 631 l = (int) v_x; 632 t = (int) v_y; 633 i_width = (int) v_width; 634 i_height = (int) v_height; 635 r = l + i_width; 636 b = t + i_height; 637 } 638 639 // MotionWidget must do Android View measure if layout changes 640 view.layout(l, t, r, b); 641 if (DEBUG) { 642 if (toUse.length > 0) { 643 Utils.log(TAG, "setView " + mod); 644 } 645 } 646 } 647 getRect(int[] toUse, double[] data, float[] path, int offset)648 void getRect(int[] toUse, double[] data, float[] path, int offset) { 649 float v_x = mX; 650 float v_y = mY; 651 float v_width = mWidth; 652 float v_height = mHeight; 653 @SuppressWarnings("unused") float delta_path = 0; 654 float rotation = 0; 655 @SuppressWarnings("unused") float alpha = 0; 656 @SuppressWarnings("unused") float rotationX = 0; 657 @SuppressWarnings("unused") float rotationY = 0; 658 float scaleX = 1; 659 float scaleY = 1; 660 float pivotX = Float.NaN; 661 float pivotY = Float.NaN; 662 float translationX = 0; 663 float translationY = 0; 664 665 @SuppressWarnings("unused") String mod; 666 667 for (int i = 0; i < toUse.length; i++) { 668 float value = (float) data[i]; 669 670 switch (toUse[i]) { 671 case OFF_POSITION: 672 delta_path = value; 673 break; 674 case OFF_X: 675 v_x = value; 676 break; 677 case OFF_Y: 678 v_y = value; 679 break; 680 case OFF_WIDTH: 681 v_width = value; 682 break; 683 case OFF_HEIGHT: 684 v_height = value; 685 break; 686 } 687 } 688 689 if (mRelativeToController != null) { 690 float rx = mRelativeToController.getCenterX(); 691 float ry = mRelativeToController.getCenterY(); 692 float radius = v_x; 693 float angle = v_y; 694 // TODO Debug angle 695 v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2); 696 v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2); 697 } 698 699 float x1 = v_x; 700 float y1 = v_y; 701 702 float x2 = v_x + v_width; 703 float y2 = y1; 704 705 float x3 = x2; 706 float y3 = v_y + v_height; 707 708 float x4 = x1; 709 float y4 = y3; 710 711 float cx = x1 + v_width / 2; 712 float cy = y1 + v_height / 2; 713 714 if (!Float.isNaN(pivotX)) { 715 cx = x1 + (x2 - x1) * pivotX; 716 } 717 if (!Float.isNaN(pivotY)) { 718 719 cy = y1 + (y3 - y1) * pivotY; 720 } 721 if (scaleX != 1) { 722 float midx = (x1 + x2) / 2; 723 x1 = (x1 - midx) * scaleX + midx; 724 x2 = (x2 - midx) * scaleX + midx; 725 x3 = (x3 - midx) * scaleX + midx; 726 x4 = (x4 - midx) * scaleX + midx; 727 } 728 if (scaleY != 1) { 729 float midy = (y1 + y3) / 2; 730 y1 = (y1 - midy) * scaleY + midy; 731 y2 = (y2 - midy) * scaleY + midy; 732 y3 = (y3 - midy) * scaleY + midy; 733 y4 = (y4 - midy) * scaleY + midy; 734 } 735 if (rotation != 0) { 736 float sin = (float) Math.sin(Math.toRadians(rotation)); 737 float cos = (float) Math.cos(Math.toRadians(rotation)); 738 float tx1 = xRotate(sin, cos, cx, cy, x1, y1); 739 float ty1 = yRotate(sin, cos, cx, cy, x1, y1); 740 float tx2 = xRotate(sin, cos, cx, cy, x2, y2); 741 float ty2 = yRotate(sin, cos, cx, cy, x2, y2); 742 float tx3 = xRotate(sin, cos, cx, cy, x3, y3); 743 float ty3 = yRotate(sin, cos, cx, cy, x3, y3); 744 float tx4 = xRotate(sin, cos, cx, cy, x4, y4); 745 float ty4 = yRotate(sin, cos, cx, cy, x4, y4); 746 x1 = tx1; 747 y1 = ty1; 748 x2 = tx2; 749 y2 = ty2; 750 x3 = tx3; 751 y3 = ty3; 752 x4 = tx4; 753 y4 = ty4; 754 } 755 756 x1 += translationX; 757 y1 += translationY; 758 x2 += translationX; 759 y2 += translationY; 760 x3 += translationX; 761 y3 += translationY; 762 x4 += translationX; 763 y4 += translationY; 764 765 path[offset++] = x1; 766 path[offset++] = y1; 767 path[offset++] = x2; 768 path[offset++] = y2; 769 path[offset++] = x3; 770 path[offset++] = y3; 771 path[offset++] = x4; 772 path[offset++] = y4; 773 } 774 775 /** 776 * mAnchorDpDt 777 */ setDpDt(float locationX, float locationY, float[] mAnchorDpDt, int[] toUse, double[] deltaData, double[] data)778 void setDpDt(float locationX, 779 float locationY, 780 float[] mAnchorDpDt, 781 int[] toUse, 782 double[] deltaData, 783 double[] data) { 784 785 float d_x = 0; 786 float d_y = 0; 787 float d_width = 0; 788 float d_height = 0; 789 790 float deltaScaleX = 0; 791 float deltaScaleY = 0; 792 793 @SuppressWarnings("unused") float mPathRotate = Float.NaN; 794 float deltaTranslationX = 0; 795 float deltaTranslationY = 0; 796 797 String mod = " dd = "; 798 for (int i = 0; i < toUse.length; i++) { 799 float deltaV = (float) deltaData[i]; 800 if (DEBUG) { 801 mod += " , D" + sNames[toUse[i]] + "/Dt= " + deltaV; 802 } 803 switch (toUse[i]) { 804 case OFF_POSITION: 805 break; 806 case OFF_X: 807 d_x = deltaV; 808 break; 809 case OFF_Y: 810 d_y = deltaV; 811 break; 812 case OFF_WIDTH: 813 d_width = deltaV; 814 break; 815 case OFF_HEIGHT: 816 d_height = deltaV; 817 break; 818 819 } 820 } 821 if (DEBUG) { 822 if (toUse.length > 0) { 823 Utils.log(TAG, "setDpDt " + mod); 824 } 825 } 826 827 float deltaX = d_x - deltaScaleX * d_width / 2; 828 float deltaY = d_y - deltaScaleY * d_height / 2; 829 float deltaWidth = d_width * (1 + deltaScaleX); 830 float deltaHeight = d_height * (1 + deltaScaleY); 831 float deltaRight = deltaX + deltaWidth; 832 float deltaBottom = deltaY + deltaHeight; 833 if (DEBUG) { 834 if (toUse.length > 0) { 835 836 Utils.log(TAG, "D x /dt =" + d_x); 837 Utils.log(TAG, "D y /dt =" + d_y); 838 Utils.log(TAG, "D width /dt =" + d_width); 839 Utils.log(TAG, "D height /dt =" + d_height); 840 Utils.log(TAG, "D deltaScaleX /dt =" + deltaScaleX); 841 Utils.log(TAG, "D deltaScaleY /dt =" + deltaScaleY); 842 Utils.log(TAG, "D deltaX /dt =" + deltaX); 843 Utils.log(TAG, "D deltaY /dt =" + deltaY); 844 Utils.log(TAG, "D deltaWidth /dt =" + deltaWidth); 845 Utils.log(TAG, "D deltaHeight /dt =" + deltaHeight); 846 Utils.log(TAG, "D deltaRight /dt =" + deltaRight); 847 Utils.log(TAG, "D deltaBottom /dt =" + deltaBottom); 848 Utils.log(TAG, "locationX =" + locationX); 849 Utils.log(TAG, "locationY =" + locationY); 850 Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX); 851 Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX); 852 } 853 } 854 855 mAnchorDpDt[0] = deltaX * (1 - locationX) + deltaRight * locationX + deltaTranslationX; 856 mAnchorDpDt[1] = deltaY * (1 - locationY) + deltaBottom * locationY + deltaTranslationY; 857 } 858 fillStandard(double[] data, int[] toUse)859 void fillStandard(double[] data, int[] toUse) { 860 float[] set = {mPosition, mX, mY, mWidth, mHeight, mPathRotate}; 861 int c = 0; 862 for (int i = 0; i < toUse.length; i++) { 863 if (toUse[i] < set.length) { 864 data[c++] = set[toUse[i]]; 865 } 866 } 867 } 868 hasCustomData(String name)869 boolean hasCustomData(String name) { 870 return mCustomAttributes.containsKey(name); 871 } 872 getCustomDataCount(String name)873 int getCustomDataCount(String name) { 874 CustomVariable a = mCustomAttributes.get(name); 875 if (a == null) { 876 return 0; 877 } 878 return a.numberOfInterpolatedValues(); 879 } 880 getCustomData(String name, double[] value, int offset)881 int getCustomData(String name, double[] value, int offset) { 882 CustomVariable a = mCustomAttributes.get(name); 883 if (a == null) { 884 return 0; 885 } else if (a.numberOfInterpolatedValues() == 1) { 886 value[offset] = a.getValueToInterpolate(); 887 return 1; 888 } else { 889 int n = a.numberOfInterpolatedValues(); 890 float[] f = new float[n]; 891 a.getValuesToInterpolate(f); 892 for (int i = 0; i < n; i++) { 893 value[offset++] = f[i]; 894 } 895 return n; 896 } 897 } 898 setBounds(float x, float y, float w, float h)899 void setBounds(float x, float y, float w, float h) { 900 this.mX = x; 901 this.mY = y; 902 mWidth = w; 903 mHeight = h; 904 } 905 906 @Override compareTo(MotionPaths o)907 public int compareTo(MotionPaths o) { 908 return Float.compare(mPosition, o.mPosition); 909 } 910 911 // @TODO: add description applyParameters(MotionWidget c)912 public void applyParameters(MotionWidget c) { 913 MotionPaths point = this; 914 point.mKeyFrameEasing = Easing.getInterpolator(c.mMotion.mTransitionEasing); 915 point.mPathMotionArc = c.mMotion.mPathMotionArc; 916 point.mAnimateRelativeTo = c.mMotion.mAnimateRelativeTo; 917 point.mPathRotate = c.mMotion.mPathRotate; 918 point.mDrawPath = c.mMotion.mDrawPath; 919 point.mAnimateCircleAngleTo = c.mMotion.mAnimateCircleAngleTo; 920 point.mProgress = c.mPropertySet.mProgress; 921 if (c.mWidgetFrame != null && c.mWidgetFrame.widget != null) { 922 point.mRelativeAngle = c.mWidgetFrame.widget.mCircleConstraintAngle; 923 } 924 925 Set<String> at = c.getCustomAttributeNames(); 926 for (String s : at) { 927 CustomVariable attr = c.getCustomAttribute(s); 928 if (attr != null && attr.isContinuous()) { 929 this.mCustomAttributes.put(s, attr); 930 } 931 } 932 } 933 934 // @TODO: add description configureRelativeTo(Motion toOrbit)935 public void configureRelativeTo(Motion toOrbit) { 936 @SuppressWarnings("unused") double[] p = toOrbit.getPos(mProgress); // get the position 937 // in the orbit 938 } 939 } 940