1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.constraintlayout.motion.widget; 17 18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.os.Build; 21 import android.util.AttributeSet; 22 import android.util.Log; 23 import android.util.SparseIntArray; 24 import android.util.TypedValue; 25 26 import androidx.constraintlayout.core.motion.utils.Oscillator; 27 import androidx.constraintlayout.motion.utils.ViewSpline; 28 import androidx.constraintlayout.motion.utils.ViewTimeCycle; 29 import androidx.constraintlayout.widget.ConstraintAttribute; 30 import androidx.constraintlayout.widget.R; 31 32 import java.util.HashMap; 33 import java.util.HashSet; 34 35 /** 36 * Defines container for a key frame of for storing KeyTimeCycles. 37 * KeyTimeCycles change post layout values of a view. 38 * 39 * 40 */ 41 public class KeyTimeCycle extends Key { 42 public static final String WAVE_PERIOD = "wavePeriod"; 43 public static final String WAVE_OFFSET = "waveOffset"; 44 public static final String WAVE_SHAPE = "waveShape"; 45 public static final int SHAPE_SIN_WAVE = Oscillator.SIN_WAVE; 46 public static final int SHAPE_SQUARE_WAVE = Oscillator.SQUARE_WAVE; 47 public static final int SHAPE_TRIANGLE_WAVE = Oscillator.TRIANGLE_WAVE; 48 public static final int SHAPE_SAW_WAVE = Oscillator.SAW_WAVE; 49 public static final int SHAPE_REVERSE_SAW_WAVE = Oscillator.REVERSE_SAW_WAVE; 50 public static final int SHAPE_COS_WAVE = Oscillator.COS_WAVE; 51 public static final int SHAPE_BOUNCE = Oscillator.BOUNCE; 52 public static final int KEY_TYPE = 3; 53 static final String NAME = "KeyTimeCycle"; 54 private static final String TAG = NAME; 55 private String mTransitionEasing; 56 private int mCurveFit = -1; 57 private float mAlpha = Float.NaN; 58 private float mElevation = Float.NaN; 59 private float mRotation = Float.NaN; 60 private float mRotationX = Float.NaN; 61 private float mRotationY = Float.NaN; 62 private float mTransitionPathRotate = Float.NaN; 63 private float mScaleX = Float.NaN; 64 private float mScaleY = Float.NaN; 65 private float mTranslationX = Float.NaN; 66 private float mTranslationY = Float.NaN; 67 private float mTranslationZ = Float.NaN; 68 private float mProgress = Float.NaN; 69 private int mWaveShape = 0; 70 private String mCustomWaveShape = null; // TODO add support of custom wave shapes 71 private float mWavePeriod = Float.NaN; 72 private float mWaveOffset = 0; 73 74 { 75 mType = KEY_TYPE; 76 mCustomConstraints = new HashMap<>(); 77 } 78 79 @Override load(Context context, AttributeSet attrs)80 public void load(Context context, AttributeSet attrs) { 81 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyTimeCycle); 82 Loader.read(this, a); 83 } 84 85 /** 86 * Gets the curve fit type this drives the interpolation 87 */ 88 89 @Override getAttributeNames(HashSet<String> attributes)90 public void getAttributeNames(HashSet<String> attributes) { 91 if (!Float.isNaN(mAlpha)) { 92 attributes.add(Key.ALPHA); 93 } 94 if (!Float.isNaN(mElevation)) { 95 attributes.add(Key.ELEVATION); 96 } 97 if (!Float.isNaN(mRotation)) { 98 attributes.add(Key.ROTATION); 99 } 100 if (!Float.isNaN(mRotationX)) { 101 attributes.add(Key.ROTATION_X); 102 } 103 if (!Float.isNaN(mRotationY)) { 104 attributes.add(Key.ROTATION_Y); 105 } 106 if (!Float.isNaN(mTranslationX)) { 107 attributes.add(Key.TRANSLATION_X); 108 } 109 if (!Float.isNaN(mTranslationY)) { 110 attributes.add(Key.TRANSLATION_Y); 111 } 112 if (!Float.isNaN(mTranslationZ)) { 113 attributes.add(Key.TRANSLATION_Z); 114 } 115 if (!Float.isNaN(mTransitionPathRotate)) { 116 attributes.add(Key.TRANSITION_PATH_ROTATE); 117 } 118 if (!Float.isNaN(mScaleX)) { 119 attributes.add(Key.SCALE_X); 120 } 121 if (!Float.isNaN(mScaleY)) { 122 attributes.add(Key.SCALE_Y); 123 } 124 if (!Float.isNaN(mProgress)) { 125 attributes.add(Key.PROGRESS); 126 } 127 if (mCustomConstraints.size() > 0) { 128 for (String s : mCustomConstraints.keySet()) { 129 attributes.add(Key.CUSTOM + "," + s); 130 } 131 } 132 } 133 134 /** 135 * put key and position into the interpolation map 136 * 137 * @param interpolation 138 */ 139 @Override setInterpolation(HashMap<String, Integer> interpolation)140 public void setInterpolation(HashMap<String, Integer> interpolation) { 141 if (mCurveFit == -1) { 142 return; 143 } 144 if (!Float.isNaN(mAlpha)) { 145 interpolation.put(Key.ALPHA, mCurveFit); 146 } 147 if (!Float.isNaN(mElevation)) { 148 interpolation.put(Key.ELEVATION, mCurveFit); 149 } 150 if (!Float.isNaN(mRotation)) { 151 interpolation.put(Key.ROTATION, mCurveFit); 152 } 153 if (!Float.isNaN(mRotationX)) { 154 interpolation.put(Key.ROTATION_X, mCurveFit); 155 } 156 if (!Float.isNaN(mRotationY)) { 157 interpolation.put(Key.ROTATION_Y, mCurveFit); 158 } 159 if (!Float.isNaN(mTranslationX)) { 160 interpolation.put(Key.TRANSLATION_X, mCurveFit); 161 } 162 if (!Float.isNaN(mTranslationY)) { 163 interpolation.put(Key.TRANSLATION_Y, mCurveFit); 164 } 165 if (!Float.isNaN(mTranslationZ)) { 166 interpolation.put(Key.TRANSLATION_Z, mCurveFit); 167 } 168 if (!Float.isNaN(mTransitionPathRotate)) { 169 interpolation.put(Key.TRANSITION_PATH_ROTATE, mCurveFit); 170 } 171 if (!Float.isNaN(mScaleX)) { 172 interpolation.put(Key.SCALE_X, mCurveFit); 173 } 174 if (!Float.isNaN(mScaleX)) { 175 interpolation.put(Key.SCALE_Y, mCurveFit); 176 } 177 if (!Float.isNaN(mProgress)) { 178 interpolation.put(Key.PROGRESS, mCurveFit); 179 } 180 if (mCustomConstraints.size() > 0) { 181 for (String s : mCustomConstraints.keySet()) { 182 interpolation.put(Key.CUSTOM + "," + s, mCurveFit); 183 } 184 } 185 } 186 187 @Override addValues(HashMap<String, ViewSpline> splines)188 public void addValues(HashMap<String, ViewSpline> splines) { 189 // This should not get called 190 throw new IllegalArgumentException(" KeyTimeCycles do not support SplineSet"); 191 } 192 193 /** 194 * Add values to TimeCycle Map 195 * 196 * @param splines 197 */ addTimeValues(HashMap<String, ViewTimeCycle> splines)198 public void addTimeValues(HashMap<String, ViewTimeCycle> splines) { 199 for (String s : splines.keySet()) { 200 ViewTimeCycle splineSet = splines.get(s); 201 if (splineSet == null) { 202 continue; 203 } 204 if (s.startsWith(Key.CUSTOM)) { 205 String cKey = s.substring(Key.CUSTOM.length() + 1); 206 ConstraintAttribute cValue = mCustomConstraints.get(cKey); 207 if (cValue != null) { 208 ((ViewTimeCycle.CustomSet) splineSet).setPoint(mFramePosition, cValue, 209 mWavePeriod, mWaveShape, mWaveOffset); 210 } 211 continue; 212 } 213 switch (s) { 214 case Key.ALPHA: 215 if (!Float.isNaN(mAlpha)) { 216 splineSet.setPoint(mFramePosition, mAlpha, 217 mWavePeriod, mWaveShape, mWaveOffset); 218 } 219 break; 220 case Key.ELEVATION: 221 if (!Float.isNaN(mElevation)) { 222 splineSet.setPoint(mFramePosition, mElevation, 223 mWavePeriod, mWaveShape, mWaveOffset); 224 } 225 break; 226 case Key.ROTATION: 227 if (!Float.isNaN(mRotation)) { 228 splineSet.setPoint(mFramePosition, mRotation, 229 mWavePeriod, mWaveShape, mWaveOffset); 230 } 231 break; 232 case Key.ROTATION_X: 233 if (!Float.isNaN(mRotationX)) { 234 splineSet.setPoint(mFramePosition, mRotationX, 235 mWavePeriod, mWaveShape, mWaveOffset); 236 } 237 break; 238 case Key.ROTATION_Y: 239 if (!Float.isNaN(mRotationY)) { 240 splineSet.setPoint(mFramePosition, mRotationY, 241 mWavePeriod, mWaveShape, mWaveOffset); 242 } 243 break; 244 case Key.TRANSITION_PATH_ROTATE: 245 if (!Float.isNaN(mTransitionPathRotate)) { 246 splineSet.setPoint(mFramePosition, mTransitionPathRotate, 247 mWavePeriod, mWaveShape, mWaveOffset); 248 } 249 break; 250 case Key.SCALE_X: 251 if (!Float.isNaN(mScaleX)) { 252 splineSet.setPoint(mFramePosition, mScaleX, 253 mWavePeriod, mWaveShape, mWaveOffset); 254 } 255 break; 256 case Key.SCALE_Y: 257 if (!Float.isNaN(mScaleY)) { 258 splineSet.setPoint(mFramePosition, mScaleY, 259 mWavePeriod, mWaveShape, mWaveOffset); 260 } 261 break; 262 case Key.TRANSLATION_X: 263 if (!Float.isNaN(mTranslationX)) { 264 splineSet.setPoint(mFramePosition, mTranslationX, 265 mWavePeriod, mWaveShape, mWaveOffset); 266 } 267 break; 268 case Key.TRANSLATION_Y: 269 if (!Float.isNaN(mTranslationY)) { 270 splineSet.setPoint(mFramePosition, mTranslationY, 271 mWavePeriod, mWaveShape, mWaveOffset); 272 } 273 break; 274 case Key.TRANSLATION_Z: 275 if (!Float.isNaN(mTranslationZ)) { 276 splineSet.setPoint(mFramePosition, mTranslationZ, 277 mWavePeriod, mWaveShape, mWaveOffset); 278 } 279 break; 280 case Key.PROGRESS: 281 if (!Float.isNaN(mProgress)) { 282 splineSet.setPoint(mFramePosition, mProgress, 283 mWavePeriod, mWaveShape, mWaveOffset); 284 } 285 break; 286 default: 287 Log.e("KeyTimeCycles", "UNKNOWN addValues \"" + s + "\""); 288 } 289 } 290 } 291 292 @Override setValue(String tag, Object value)293 public void setValue(String tag, Object value) { 294 switch (tag) { 295 case Key.ALPHA: 296 mAlpha = toFloat(value); 297 break; 298 case CURVEFIT: 299 mCurveFit = toInt(value); 300 break; 301 case ELEVATION: 302 mElevation = toFloat(value); 303 break; 304 case MOTIONPROGRESS: 305 mProgress = toFloat(value); 306 break; 307 case ROTATION: 308 mRotation = toFloat(value); 309 break; 310 case ROTATION_X: 311 mRotationX = toFloat(value); 312 break; 313 case ROTATION_Y: 314 mRotationY = toFloat(value); 315 break; 316 case SCALE_X: 317 mScaleX = toFloat(value); 318 break; 319 case SCALE_Y: 320 mScaleY = toFloat(value); 321 break; 322 case TRANSITIONEASING: 323 mTransitionEasing = value.toString(); 324 break; 325 case TRANSITION_PATH_ROTATE: 326 mTransitionPathRotate = toFloat(value); 327 break; 328 case TRANSLATION_X: 329 mTranslationX = toFloat(value); 330 break; 331 case TRANSLATION_Y: 332 mTranslationY = toFloat(value); 333 break; 334 case TRANSLATION_Z: 335 mTranslationZ = toFloat(value); 336 break; 337 case WAVE_PERIOD: 338 mWavePeriod = toFloat(value); 339 break; 340 case WAVE_OFFSET: 341 mWaveOffset = toFloat(value); 342 break; 343 case WAVE_SHAPE: 344 if (value instanceof Integer) { 345 mWaveShape = toInt(value); 346 } else { 347 mWaveShape = Oscillator.CUSTOM; 348 mCustomWaveShape = value.toString(); 349 } 350 break; 351 } 352 353 } 354 355 /** 356 * Copy the key 357 * 358 * @param src to be copied 359 * @return self 360 */ 361 @Override copy(Key src)362 public Key copy(Key src) { 363 super.copy(src); 364 KeyTimeCycle k = (KeyTimeCycle) src; 365 mTransitionEasing = k.mTransitionEasing; 366 mCurveFit = k.mCurveFit; 367 mWaveShape = k.mWaveShape; 368 mWavePeriod = k.mWavePeriod; 369 mWaveOffset = k.mWaveOffset; 370 mProgress = k.mProgress; 371 mAlpha = k.mAlpha; 372 mElevation = k.mElevation; 373 mRotation = k.mRotation; 374 mTransitionPathRotate = k.mTransitionPathRotate; 375 mRotationX = k.mRotationX; 376 mRotationY = k.mRotationY; 377 mScaleX = k.mScaleX; 378 mScaleY = k.mScaleY; 379 mTranslationX = k.mTranslationX; 380 mTranslationY = k.mTranslationY; 381 mTranslationZ = k.mTranslationZ; 382 mCustomWaveShape = k.mCustomWaveShape; 383 return this; 384 } 385 386 /** 387 * Clone this KeyAttributes 388 * 389 * @return 390 */ 391 @Override clone()392 public Key clone() { 393 return new KeyTimeCycle().copy(this); 394 } 395 396 private static class Loader { 397 private static final int ANDROID_ALPHA = 1; 398 private static final int ANDROID_ELEVATION = 2; 399 private static final int ANDROID_ROTATION = 4; 400 private static final int ANDROID_ROTATION_X = 5; 401 private static final int ANDROID_ROTATION_Y = 6; 402 private static final int TRANSITION_PATH_ROTATE = 8; 403 private static final int ANDROID_SCALE_X = 7; 404 private static final int TRANSITION_EASING = 9; 405 private static final int TARGET_ID = 10; 406 private static final int FRAME_POSITION = 12; 407 private static final int CURVE_FIT = 13; 408 private static final int ANDROID_SCALE_Y = 14; 409 private static final int ANDROID_TRANSLATION_X = 15; 410 private static final int ANDROID_TRANSLATION_Y = 16; 411 private static final int ANDROID_TRANSLATION_Z = 17; 412 private static final int PROGRESS = 18; 413 private static final int WAVE_SHAPE = 19; 414 private static final int WAVE_PERIOD = 20; 415 private static final int WAVE_OFFSET = 21; 416 private static SparseIntArray sAttrMap = new SparseIntArray(); 417 418 static { sAttrMap.append(R.styleable.KeyTimeCycle_android_alpha, ANDROID_ALPHA)419 sAttrMap.append(R.styleable.KeyTimeCycle_android_alpha, ANDROID_ALPHA); sAttrMap.append(R.styleable.KeyTimeCycle_android_elevation, ANDROID_ELEVATION)420 sAttrMap.append(R.styleable.KeyTimeCycle_android_elevation, ANDROID_ELEVATION); sAttrMap.append(R.styleable.KeyTimeCycle_android_rotation, ANDROID_ROTATION)421 sAttrMap.append(R.styleable.KeyTimeCycle_android_rotation, ANDROID_ROTATION); sAttrMap.append(R.styleable.KeyTimeCycle_android_rotationX, ANDROID_ROTATION_X)422 sAttrMap.append(R.styleable.KeyTimeCycle_android_rotationX, ANDROID_ROTATION_X); sAttrMap.append(R.styleable.KeyTimeCycle_android_rotationY, ANDROID_ROTATION_Y)423 sAttrMap.append(R.styleable.KeyTimeCycle_android_rotationY, ANDROID_ROTATION_Y); sAttrMap.append(R.styleable.KeyTimeCycle_android_scaleX, ANDROID_SCALE_X)424 sAttrMap.append(R.styleable.KeyTimeCycle_android_scaleX, ANDROID_SCALE_X); sAttrMap.append(R.styleable.KeyTimeCycle_transitionPathRotate, TRANSITION_PATH_ROTATE)425 sAttrMap.append(R.styleable.KeyTimeCycle_transitionPathRotate, TRANSITION_PATH_ROTATE); sAttrMap.append(R.styleable.KeyTimeCycle_transitionEasing, TRANSITION_EASING)426 sAttrMap.append(R.styleable.KeyTimeCycle_transitionEasing, TRANSITION_EASING); sAttrMap.append(R.styleable.KeyTimeCycle_motionTarget, TARGET_ID)427 sAttrMap.append(R.styleable.KeyTimeCycle_motionTarget, TARGET_ID); sAttrMap.append(R.styleable.KeyTimeCycle_framePosition, FRAME_POSITION)428 sAttrMap.append(R.styleable.KeyTimeCycle_framePosition, FRAME_POSITION); sAttrMap.append(R.styleable.KeyTimeCycle_curveFit, CURVE_FIT)429 sAttrMap.append(R.styleable.KeyTimeCycle_curveFit, CURVE_FIT); sAttrMap.append(R.styleable.KeyTimeCycle_android_scaleY, ANDROID_SCALE_Y)430 sAttrMap.append(R.styleable.KeyTimeCycle_android_scaleY, ANDROID_SCALE_Y); sAttrMap.append(R.styleable.KeyTimeCycle_android_translationX, ANDROID_TRANSLATION_X)431 sAttrMap.append(R.styleable.KeyTimeCycle_android_translationX, ANDROID_TRANSLATION_X); sAttrMap.append(R.styleable.KeyTimeCycle_android_translationY, ANDROID_TRANSLATION_Y)432 sAttrMap.append(R.styleable.KeyTimeCycle_android_translationY, ANDROID_TRANSLATION_Y); sAttrMap.append(R.styleable.KeyTimeCycle_android_translationZ, ANDROID_TRANSLATION_Z)433 sAttrMap.append(R.styleable.KeyTimeCycle_android_translationZ, ANDROID_TRANSLATION_Z); sAttrMap.append(R.styleable.KeyTimeCycle_motionProgress, PROGRESS)434 sAttrMap.append(R.styleable.KeyTimeCycle_motionProgress, PROGRESS); sAttrMap.append(R.styleable.KeyTimeCycle_wavePeriod, WAVE_PERIOD)435 sAttrMap.append(R.styleable.KeyTimeCycle_wavePeriod, WAVE_PERIOD); sAttrMap.append(R.styleable.KeyTimeCycle_waveOffset, WAVE_OFFSET)436 sAttrMap.append(R.styleable.KeyTimeCycle_waveOffset, WAVE_OFFSET); sAttrMap.append(R.styleable.KeyTimeCycle_waveShape, WAVE_SHAPE)437 sAttrMap.append(R.styleable.KeyTimeCycle_waveShape, WAVE_SHAPE); 438 } 439 read(KeyTimeCycle c, TypedArray a)440 public static void read(KeyTimeCycle c, TypedArray a) { 441 final int n = a.getIndexCount(); 442 for (int i = 0; i < n; i++) { 443 int attr = a.getIndex(i); 444 switch (sAttrMap.get(attr)) { 445 case TARGET_ID: 446 if (MotionLayout.IS_IN_EDIT_MODE) { 447 c.mTargetId = a.getResourceId(attr, c.mTargetId); 448 if (c.mTargetId == -1) { 449 c.mTargetString = a.getString(attr); 450 } 451 } else { 452 if (a.peekValue(attr).type == TypedValue.TYPE_STRING) { 453 c.mTargetString = a.getString(attr); 454 } else { 455 c.mTargetId = a.getResourceId(attr, c.mTargetId); 456 } 457 } 458 break; 459 case FRAME_POSITION: 460 c.mFramePosition = a.getInt(attr, c.mFramePosition); 461 break; 462 case ANDROID_ALPHA: 463 c.mAlpha = a.getFloat(attr, c.mAlpha); 464 break; 465 case ANDROID_ELEVATION: 466 c.mElevation = a.getDimension(attr, c.mElevation); 467 break; 468 case ANDROID_ROTATION: 469 c.mRotation = a.getFloat(attr, c.mRotation); 470 break; 471 case CURVE_FIT: 472 c.mCurveFit = a.getInteger(attr, c.mCurveFit); 473 break; 474 case WAVE_SHAPE: 475 if (a.peekValue(attr).type == TypedValue.TYPE_STRING) { 476 c.mCustomWaveShape = a.getString(attr); 477 c.mWaveShape = Oscillator.CUSTOM; 478 } else { 479 c.mWaveShape = a.getInt(attr, c.mWaveShape); 480 } 481 break; 482 case WAVE_PERIOD: 483 c.mWavePeriod = a.getFloat(attr, c.mWavePeriod); 484 break; 485 case WAVE_OFFSET: 486 TypedValue type = a.peekValue(attr); 487 if (type.type == TypedValue.TYPE_DIMENSION) { 488 c.mWaveOffset = a.getDimension(attr, c.mWaveOffset); 489 } else { 490 c.mWaveOffset = a.getFloat(attr, c.mWaveOffset); 491 } 492 break; 493 case ANDROID_SCALE_X: 494 c.mScaleX = a.getFloat(attr, c.mScaleX); 495 break; 496 case ANDROID_ROTATION_X: 497 c.mRotationX = a.getFloat(attr, c.mRotationX); 498 break; 499 case ANDROID_ROTATION_Y: 500 c.mRotationY = a.getFloat(attr, c.mRotationY); 501 break; 502 case TRANSITION_EASING: 503 c.mTransitionEasing = a.getString(attr); 504 break; 505 case ANDROID_SCALE_Y: 506 c.mScaleY = a.getFloat(attr, c.mScaleY); 507 break; 508 case TRANSITION_PATH_ROTATE: 509 c.mTransitionPathRotate = a.getFloat(attr, c.mTransitionPathRotate); 510 break; 511 case ANDROID_TRANSLATION_X: 512 c.mTranslationX = a.getDimension(attr, c.mTranslationX); 513 break; 514 case ANDROID_TRANSLATION_Y: 515 c.mTranslationY = a.getDimension(attr, c.mTranslationY); 516 break; 517 case ANDROID_TRANSLATION_Z: 518 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 519 c.mTranslationZ = a.getDimension(attr, c.mTranslationZ); 520 } 521 break; 522 case PROGRESS: 523 c.mProgress = a.getFloat(attr, c.mProgress); 524 break; 525 default: 526 Log.e(NAME, "unused attribute 0x" + Integer.toHexString(attr) 527 + " " + sAttrMap.get(attr)); 528 break; 529 } 530 } 531 } 532 } 533 } 534