1 /* 2 * Copyright (C) 2020 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.utils; 17 18 import android.os.Build; 19 import android.util.Log; 20 import android.util.SparseArray; 21 import android.view.View; 22 23 import androidx.constraintlayout.core.motion.utils.CurveFit; 24 import androidx.constraintlayout.core.motion.utils.KeyCache; 25 import androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet; 26 import androidx.constraintlayout.motion.widget.Key; 27 import androidx.constraintlayout.motion.widget.MotionLayout; 28 import androidx.constraintlayout.widget.ConstraintAttribute; 29 30 import java.lang.reflect.InvocationTargetException; 31 import java.lang.reflect.Method; 32 33 /** 34 * This engine allows manipulation of attributes by wave shapes oscillating in time 35 * 36 * 37 */ 38 public abstract class ViewTimeCycle extends TimeCycleSplineSet { 39 private static final String TAG = "ViewTimeCycle"; 40 41 /** 42 * Set the time cycle parameters 43 * @param view 44 * @param t 45 * @param time 46 * @param cache 47 * @return 48 */ setProperty(View view, float t, long time, KeyCache cache)49 public abstract boolean setProperty(View view, float t, long time, KeyCache cache); 50 51 /** 52 * get a value from the time cycle 53 * @param pos 54 * @param time 55 * @param view 56 * @param cache 57 * @return 58 */ get(float pos, long time, View view, KeyCache cache)59 public float get(float pos, long time, View view, KeyCache cache) { 60 mCurveFit.getPos(pos, mCache); 61 float period = mCache[CURVE_PERIOD]; 62 if (period == 0) { 63 mContinue = false; 64 return mCache[CURVE_OFFSET]; 65 } 66 if (Float.isNaN(mLastCycle)) { // it has not been set 67 mLastCycle = cache.getFloatValue(view, mType, 0); // check the cache 68 if (Float.isNaN(mLastCycle)) { // not in cache so set to 0 (start) 69 mLastCycle = 0; 70 } 71 } 72 long delta_time = time - mLastTime; 73 mLastCycle = (float) ((mLastCycle + delta_time * 1E-9 * period) % 1.0); 74 cache.setFloatValue(view, mType, 0, mLastCycle); 75 mLastTime = time; 76 float v = mCache[CURVE_VALUE]; 77 float wave = calcWave(mLastCycle); 78 float offset = mCache[CURVE_OFFSET]; 79 float value = v * wave + offset; 80 mContinue = v != 0.0f || period != 0.0f; 81 return value; 82 } 83 84 /** 85 * make a custom time cycle 86 * @param str 87 * @param attrList 88 * @return 89 */ makeCustomSpline(String str, SparseArray<ConstraintAttribute> attrList)90 public static ViewTimeCycle makeCustomSpline(String str, 91 SparseArray<ConstraintAttribute> attrList) { 92 return new CustomSet(str, attrList); 93 } 94 95 /** 96 * Make a time cycle spline 97 * @param str 98 * @param currentTime 99 * @return 100 */ makeSpline(String str, long currentTime)101 public static ViewTimeCycle makeSpline(String str, long currentTime) { 102 ViewTimeCycle timeCycle; 103 switch (str) { 104 case Key.ALPHA: 105 timeCycle = new AlphaSet(); 106 break; 107 case Key.ELEVATION: 108 timeCycle = new ElevationSet(); 109 break; 110 case Key.ROTATION: 111 timeCycle = new RotationSet(); 112 break; 113 case Key.ROTATION_X: 114 timeCycle = new RotationXset(); 115 break; 116 case Key.ROTATION_Y: 117 timeCycle = new RotationYset(); 118 break; 119 case Key.TRANSITION_PATH_ROTATE: 120 timeCycle = new PathRotate(); 121 break; 122 case Key.SCALE_X: 123 timeCycle = new ScaleXset(); 124 break; 125 case Key.SCALE_Y: 126 timeCycle = new ScaleYset(); 127 break; 128 case Key.TRANSLATION_X: 129 timeCycle = new TranslationXset(); 130 break; 131 case Key.TRANSLATION_Y: 132 timeCycle = new TranslationYset(); 133 break; 134 case Key.TRANSLATION_Z: 135 timeCycle = new TranslationZset(); 136 break; 137 case Key.PROGRESS: 138 timeCycle = new ProgressSet(); 139 break; 140 default: 141 return null; 142 } 143 timeCycle.setStartTime(currentTime); 144 return timeCycle; 145 } 146 147 static class ElevationSet extends ViewTimeCycle { 148 @Override setProperty(View view, float t, long time, KeyCache cache)149 public boolean setProperty(View view, float t, long time, KeyCache cache) { 150 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 151 view.setElevation(get(t, time, view, cache)); 152 } 153 return mContinue; 154 } 155 } 156 157 static class AlphaSet extends ViewTimeCycle { 158 @Override setProperty(View view, float t, long time, KeyCache cache)159 public boolean setProperty(View view, float t, long time, KeyCache cache) { 160 view.setAlpha(get(t, time, view, cache)); 161 return mContinue; 162 } 163 } 164 165 static class RotationSet extends ViewTimeCycle { 166 @Override setProperty(View view, float t, long time, KeyCache cache)167 public boolean setProperty(View view, float t, long time, KeyCache cache) { 168 view.setRotation(get(t, time, view, cache)); 169 return mContinue; 170 } 171 } 172 173 static class RotationXset extends ViewTimeCycle { 174 @Override setProperty(View view, float t, long time, KeyCache cache)175 public boolean setProperty(View view, float t, long time, KeyCache cache) { 176 view.setRotationX(get(t, time, view, cache)); 177 return mContinue; 178 } 179 } 180 181 static class RotationYset extends ViewTimeCycle { 182 @Override setProperty(View view, float t, long time, KeyCache cache)183 public boolean setProperty(View view, float t, long time, KeyCache cache) { 184 view.setRotationY(get(t, time, view, cache)); 185 return mContinue; 186 } 187 } 188 189 public static class PathRotate extends ViewTimeCycle { 190 @Override setProperty(View view, float t, long time, KeyCache cache)191 public boolean setProperty(View view, float t, long time, KeyCache cache) { 192 return mContinue; 193 } 194 195 /** 196 * DO NOT CALL 197 * @param view 198 * @param cache 199 * @param t 200 * @param time 201 * @param dx 202 * @param dy 203 * @return 204 */ setPathRotate(View view, KeyCache cache, float t, long time, double dx, double dy)205 public boolean setPathRotate(View view, 206 KeyCache cache, 207 float t, 208 long time, 209 double dx, 210 double dy) { 211 view.setRotation(get(t, time, view, cache) 212 + (float) Math.toDegrees(Math.atan2(dy, dx))); 213 return mContinue; 214 } 215 } 216 217 static class ScaleXset extends ViewTimeCycle { 218 @Override setProperty(View view, float t, long time, KeyCache cache)219 public boolean setProperty(View view, float t, long time, KeyCache cache) { 220 view.setScaleX(get(t, time, view, cache)); 221 return mContinue; 222 } 223 } 224 225 static class ScaleYset extends ViewTimeCycle { 226 @Override setProperty(View view, float t, long time, KeyCache cache)227 public boolean setProperty(View view, float t, long time, KeyCache cache) { 228 view.setScaleY(get(t, time, view, cache)); 229 return mContinue; 230 } 231 } 232 233 static class TranslationXset extends ViewTimeCycle { 234 @Override setProperty(View view, float t, long time, KeyCache cache)235 public boolean setProperty(View view, float t, long time, KeyCache cache) { 236 view.setTranslationX(get(t, time, view, cache)); 237 return mContinue; 238 } 239 } 240 241 static class TranslationYset extends ViewTimeCycle { 242 @Override setProperty(View view, float t, long time, KeyCache cache)243 public boolean setProperty(View view, float t, long time, KeyCache cache) { 244 view.setTranslationY(get(t, time, view, cache)); 245 return mContinue; 246 } 247 } 248 249 static class TranslationZset extends ViewTimeCycle { 250 @Override setProperty(View view, float t, long time, KeyCache cache)251 public boolean setProperty(View view, float t, long time, KeyCache cache) { 252 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 253 view.setTranslationZ(get(t, time, view, cache)); 254 } 255 return mContinue; 256 } 257 } 258 259 public static class CustomSet extends ViewTimeCycle { 260 String mAttributeName; 261 SparseArray<ConstraintAttribute> mConstraintAttributeList; 262 SparseArray<float[]> mWaveProperties = new SparseArray<>(); 263 float[] mTempValues; 264 265 @SuppressWarnings("StringSplitter") CustomSet(String attribute, SparseArray<ConstraintAttribute> attrList)266 public CustomSet(String attribute, SparseArray<ConstraintAttribute> attrList) { 267 mAttributeName = attribute.split(",")[1]; 268 mConstraintAttributeList = attrList; 269 } 270 271 /** 272 * Setup the curve 273 * @param curveType 274 */ 275 @Override setup(int curveType)276 public void setup(int curveType) { 277 int size = mConstraintAttributeList.size(); 278 int dimensionality = 279 mConstraintAttributeList.valueAt(0).numberOfInterpolatedValues(); 280 double[] time = new double[size]; 281 mTempValues = new float[dimensionality + 2]; 282 mCache = new float[dimensionality]; 283 double[][] values = new double[size][dimensionality + 2]; 284 for (int i = 0; i < size; i++) { 285 int key = mConstraintAttributeList.keyAt(i); 286 ConstraintAttribute ca = mConstraintAttributeList.valueAt(i); 287 float[] waveProp = mWaveProperties.valueAt(i); 288 time[i] = key * 1E-2; 289 ca.getValuesToInterpolate(mTempValues); 290 for (int k = 0; k < mTempValues.length; k++) { 291 values[i][k] = mTempValues[k]; 292 } 293 values[i][dimensionality] = waveProp[0]; 294 values[i][dimensionality + 1] = waveProp[1]; 295 } 296 mCurveFit = CurveFit.get(curveType, time, values); 297 } 298 299 // @TODO: add description 300 301 /** 302 * @param position 303 * @param value 304 * @param period 305 * @param shape 306 * @param offset 307 */ 308 @Override setPoint(int position, float value, float period, int shape, float offset)309 public void setPoint(int position, float value, float period, int shape, float offset) { 310 throw new RuntimeException("Wrong call for custom attribute"); 311 } 312 313 /** 314 * set the keyTimePoint 315 * @param position 316 * @param value 317 * @param period 318 * @param shape 319 * @param offset 320 */ setPoint(int position, ConstraintAttribute value, float period, int shape, float offset)321 public void setPoint(int position, ConstraintAttribute value, 322 float period, 323 int shape, 324 float offset) { 325 mConstraintAttributeList.append(position, value); 326 mWaveProperties.append(position, new float[]{period, offset}); 327 mWaveShape = Math.max(mWaveShape, shape); // the highest value shape is chosen 328 } 329 330 /** 331 * Set the property of the view given the position the current time 332 * @param view the view to set the property of 333 * @param t the position on the curve 334 * @param time the current time 335 * @param cache the cache used to keep the previous position 336 * @return true if it will need repaint 337 */ 338 @Override setProperty(View view, float t, long time, KeyCache cache)339 public boolean setProperty(View view, float t, long time, KeyCache cache) { 340 mCurveFit.getPos(t, mTempValues); 341 float period = mTempValues[mTempValues.length - 2]; 342 float offset = mTempValues[mTempValues.length - 1]; 343 long delta_time = time - mLastTime; 344 345 if (Float.isNaN(mLastCycle)) { // it has not been set 346 mLastCycle = cache.getFloatValue(view, mAttributeName, 0); // check the cache 347 if (Float.isNaN(mLastCycle)) { // not in cache so set to 0 (start) 348 mLastCycle = 0; 349 } 350 } 351 352 mLastCycle = (float) ((mLastCycle + delta_time * 1E-9 * period) % 1.0); 353 mLastTime = time; 354 float wave = calcWave(mLastCycle); 355 mContinue = false; 356 for (int i = 0; i < mCache.length; i++) { 357 mContinue |= mTempValues[i] != 0.0; 358 mCache[i] = mTempValues[i] * wave + offset; 359 } 360 CustomSupport.setInterpolatedValue(mConstraintAttributeList.valueAt(0), 361 view, mCache); 362 if (period != 0.0f) { 363 mContinue = true; 364 } 365 return mContinue; 366 } 367 } 368 369 static class ProgressSet extends ViewTimeCycle { 370 boolean mNoMethod = false; 371 372 @Override setProperty(View view, float t, long time, KeyCache cache)373 public boolean setProperty(View view, float t, long time, KeyCache cache) { 374 if (view instanceof MotionLayout) { 375 ((MotionLayout) view).setProgress(get(t, time, view, cache)); 376 } else { 377 if (mNoMethod) { 378 return false; 379 } 380 Method method = null; 381 try { 382 method = view.getClass().getMethod("setProgress", Float.TYPE); 383 } catch (NoSuchMethodException e) { 384 mNoMethod = true; 385 } 386 if (method != null) { 387 try { 388 method.invoke(view, get(t, time, view, cache)); 389 } catch (IllegalAccessException e) { 390 Log.e(TAG, "unable to setProgress", e); 391 } catch (InvocationTargetException e) { 392 Log.e(TAG, "unable to setProgress", e); 393 } 394 } 395 } 396 return mContinue; 397 } 398 } 399 } 400