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