• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.support.v17.leanback.widget;
18 
19 import android.animation.PropertyValuesHolder;
20 import android.support.v17.leanback.widget.Parallax.FloatProperty;
21 import android.support.v17.leanback.widget.Parallax.FloatPropertyMarkerValue;
22 import android.support.v17.leanback.widget.Parallax.IntProperty;
23 import android.support.v17.leanback.widget.Parallax.PropertyMarkerValue;
24 import android.util.Property;
25 
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * ParallaxEffect class drives changes in {@link ParallaxTarget} in response to changes in
31  * variables defined in {@link Parallax}.
32  * <p>
33  * ParallaxEffect has a list of {@link Parallax.PropertyMarkerValue}s which represents the range of
34  * values that source variables can take. The main function is
35  * {@link ParallaxEffect#performMapping(Parallax)} which computes a fraction between 0 and 1
36  * based on the current values of variables in {@link Parallax}. As the parallax effect goes
37  * on, the fraction increases from 0 at beginning to 1 at the end. Then the fraction is passed on
38  * to {@link ParallaxTarget#update(float)}.
39  * <p>
40  * App use {@link Parallax#addEffect(PropertyMarkerValue...)} to create a ParallaxEffect.
41  */
42 public abstract class ParallaxEffect {
43 
44     final List<Parallax.PropertyMarkerValue> mMarkerValues = new ArrayList(2);
45     final List<Float> mWeights = new ArrayList<Float>(2);
46     final List<Float> mTotalWeights = new ArrayList<Float>(2);
47     final List<ParallaxTarget> mTargets = new ArrayList<ParallaxTarget>(4);
48 
49     /**
50      * Only accessible from package
51      */
ParallaxEffect()52     ParallaxEffect() {
53     }
54 
55     /**
56      * Returns the list of {@link PropertyMarkerValue}s, which represents the range of values that
57      * source variables can take.
58      *
59      * @return A list of {@link Parallax.PropertyMarkerValue}s.
60      * @see #performMapping(Parallax)
61      */
getPropertyRanges()62     public final List<Parallax.PropertyMarkerValue> getPropertyRanges() {
63         return mMarkerValues;
64     }
65 
66     /**
67      * Returns a list of Float objects that represents weight associated with each variable range.
68      * Weights are used when there are three or more marker values.
69      *
70      * @return A list of Float objects that represents weight associated with each variable range.
71      * @hide
72      */
getWeights()73     public final List<Float> getWeights() {
74         return mWeights;
75     }
76 
77     /**
78      * Sets the list of {@link PropertyMarkerValue}s, which represents the range of values that
79      * source variables can take.
80      *
81      * @param markerValues A list of {@link PropertyMarkerValue}s.
82      * @see #performMapping(Parallax)
83      */
setPropertyRanges(Parallax.PropertyMarkerValue... markerValues)84     public final void setPropertyRanges(Parallax.PropertyMarkerValue... markerValues) {
85         mMarkerValues.clear();
86         for (Parallax.PropertyMarkerValue markerValue : markerValues) {
87             mMarkerValues.add(markerValue);
88         }
89     }
90 
91     /**
92      * Sets a list of Float objects that represents weight associated with each variable range.
93      * Weights are used when there are three or more marker values.
94      *
95      * @param weights A list of Float objects that represents weight associated with each variable
96      *                range.
97      * @hide
98      */
setWeights(float... weights)99     public final void setWeights(float... weights) {
100         for (float weight : weights) {
101             if (weight <= 0) {
102                 throw new IllegalArgumentException();
103             }
104         }
105         mWeights.clear();
106         mTotalWeights.clear();
107         float totalWeight = 0f;
108         for (float weight : weights) {
109             mWeights.add(weight);
110             totalWeight += weight;
111             mTotalWeights.add(totalWeight);
112         }
113     }
114 
115     /**
116      * Sets a list of Float objects that represents weight associated with each variable range.
117      * Weights are used when there are three or more marker values.
118      *
119      * @param weights A list of Float objects that represents weight associated with each variable
120      *                range.
121      * @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
122      * @hide
123      */
weights(float... weights)124     public final ParallaxEffect weights(float... weights) {
125         setWeights(weights);
126         return this;
127     }
128 
129     /**
130      * Add a ParallaxTarget to run parallax effect.
131      *
132      * @param target ParallaxTarget to add.
133      */
addTarget(ParallaxTarget target)134     public final void addTarget(ParallaxTarget target) {
135         mTargets.add(target);
136     }
137 
138     /**
139      * Add a ParallaxTarget to run parallax effect.
140      *
141      * @param target ParallaxTarget to add.
142      * @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
143      */
target(ParallaxTarget target)144     public final ParallaxEffect target(ParallaxTarget target) {
145         mTargets.add(target);
146         return this;
147     }
148 
149     /**
150      * Creates a {@link ParallaxTarget} from {@link PropertyValuesHolder} and adds it to the list
151      * of targets.
152      *
153      * @param targetObject Target object for PropertyValuesHolderTarget.
154      * @param values       PropertyValuesHolder for PropertyValuesHolderTarget.
155      * @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
156      */
target(Object targetObject, PropertyValuesHolder values)157     public final ParallaxEffect target(Object targetObject, PropertyValuesHolder values) {
158         mTargets.add(new ParallaxTarget.PropertyValuesHolderTarget(targetObject, values));
159         return this;
160     }
161 
162     /**
163      * Creates a {@link ParallaxTarget} using direct mapping from source property into target
164      * property, the new {@link ParallaxTarget} will be added to its list of targets.
165      *
166      * @param targetObject Target object for property.
167      * @param targetProperty The target property that will receive values.
168      * @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
169      * @param <T> Type of target object.
170      * @param <V> Type of target property value, either Integer or Float.
171      * @see ParallaxTarget#isDirectMapping()
172      */
target(T targetObject, Property<T, V> targetProperty)173     public final <T, V extends Number> ParallaxEffect target(T targetObject,
174             Property<T, V> targetProperty) {
175         mTargets.add(new ParallaxTarget.DirectPropertyTarget(targetObject, targetProperty));
176         return this;
177     }
178 
179     /**
180      * Returns the list of {@link ParallaxTarget} objects.
181      *
182      * @return The list of {@link ParallaxTarget} objects.
183      */
getTargets()184     public final List<ParallaxTarget> getTargets() {
185         return mTargets;
186     }
187 
188     /**
189      * Remove a {@link ParallaxTarget} object from the list.
190      * @param target The {@link ParallaxTarget} object to be removed.
191      */
removeTarget(ParallaxTarget target)192     public final void removeTarget(ParallaxTarget target) {
193         mTargets.remove(target);
194     }
195 
196     /**
197      * Perform mapping from {@link Parallax} to list of {@link ParallaxTarget}.
198      */
performMapping(Parallax source)199     public final void performMapping(Parallax source) {
200         if (mMarkerValues.size() < 2) {
201             return;
202         }
203         if (this instanceof IntEffect) {
204             source.verifyIntProperties();
205         } else {
206             source.verifyFloatProperties();
207         }
208         boolean fractionCalculated = false;
209         float fraction = 0;
210         Number directValue = null;
211         for (int i = 0; i < mTargets.size(); i++) {
212             ParallaxTarget target = mTargets.get(i);
213             if (target.isDirectMapping()) {
214                 if (directValue == null) {
215                     directValue = calculateDirectValue(source);
216                 }
217                 target.directUpdate(directValue);
218             } else {
219                 if (!fractionCalculated) {
220                     fractionCalculated = true;
221                     fraction = calculateFraction(source);
222                 }
223                 target.update(fraction);
224             }
225         }
226     }
227 
228     /**
229      * This method is expected to compute a fraction between 0 and 1 based on the current values of
230      * variables in {@link Parallax}. As the parallax effect goes on, the fraction increases
231      * from 0 at beginning to 1 at the end.
232      *
233      * @return Float value between 0 and 1.
234      */
calculateFraction(Parallax source)235     abstract float calculateFraction(Parallax source);
236 
237     /**
238      * This method is expected to get the current value of the single {@link IntProperty} or
239      * {@link FloatProperty}.
240      *
241      * @return Current value of the single {@link IntProperty} or {@link FloatProperty}.
242      */
calculateDirectValue(Parallax source)243     abstract Number calculateDirectValue(Parallax source);
244 
245     /**
246      * When there are multiple ranges (aka three or more markerValues),  this method adjust the
247      * fraction inside a range to fraction of whole range.
248      * e.g. four marker values, three weight values: 6, 2, 2.  totalWeights are 6, 8, 10
249      * When markerValueIndex is 3, the fraction is inside last range.
250      * adjusted_fraction = 8 / 10 + 2 / 10 * fraction.
251      */
getFractionWithWeightAdjusted(float fraction, int markerValueIndex)252     final float getFractionWithWeightAdjusted(float fraction, int markerValueIndex) {
253         // when there are three or more markerValues, take weight into consideration.
254         if (mMarkerValues.size() >= 3) {
255             final boolean hasWeightsDefined = mWeights.size() == mMarkerValues.size() - 1;
256             if (hasWeightsDefined) {
257                 // use weights user defined
258                 final float allWeights = mTotalWeights.get(mTotalWeights.size() - 1);
259                 fraction = fraction * mWeights.get(markerValueIndex - 1) / allWeights;
260                 if (markerValueIndex >= 2) {
261                     fraction += mTotalWeights.get(markerValueIndex - 2) / allWeights;
262                 }
263             } else {
264                 // assume each range has same weight.
265                 final float allWeights =  mMarkerValues.size() - 1;
266                 fraction = fraction / allWeights;
267                 if (markerValueIndex >= 2) {
268                     fraction += (float) (markerValueIndex - 1) / allWeights;
269                 }
270             }
271         }
272         return fraction;
273     }
274 
275     /**
276      * Implementation of {@link ParallaxEffect} for integer type.
277      */
278     static final class IntEffect extends ParallaxEffect {
279 
280         @Override
calculateDirectValue(Parallax source)281         Number calculateDirectValue(Parallax source) {
282             if (mMarkerValues.size() != 2) {
283                 throw new RuntimeException("Must use two marker values for direct mapping");
284             }
285             if (mMarkerValues.get(0).getProperty() != mMarkerValues.get(1).getProperty()) {
286                 throw new RuntimeException(
287                         "Marker value must use same Property for direct mapping");
288             }
289             int value1 = ((Parallax.IntPropertyMarkerValue) mMarkerValues.get(0))
290                     .getMarkerValue(source);
291             int value2 = ((Parallax.IntPropertyMarkerValue) mMarkerValues.get(1))
292                     .getMarkerValue(source);
293             if (value1 > value2) {
294                 int swapValue = value2;
295                 value2 = value1;
296                 value1 = swapValue;
297             }
298 
299             Number currentValue = ((IntProperty) mMarkerValues.get(0).getProperty()).get(source);
300             if (currentValue.intValue() < value1) {
301                 currentValue = value1;
302             } else if (currentValue.intValue() > value2) {
303                 currentValue = value2;
304             }
305             return currentValue;
306         }
307 
308         @Override
calculateFraction(Parallax source)309         float calculateFraction(Parallax source) {
310             int lastIndex = 0;
311             int lastValue = 0;
312             int lastMarkerValue = 0;
313             // go through all markerValues, find first markerValue that current value is less than.
314             for (int i = 0; i <  mMarkerValues.size(); i++) {
315                 Parallax.IntPropertyMarkerValue k =  (Parallax.IntPropertyMarkerValue)
316                         mMarkerValues.get(i);
317                 int index = k.getProperty().getIndex();
318                 int markerValue = k.getMarkerValue(source);
319                 int currentValue = source.getIntPropertyValue(index);
320 
321                 float fraction;
322                 if (i == 0) {
323                     if (currentValue >= markerValue) {
324                         return 0f;
325                     }
326                 } else {
327                     if (lastIndex == index && lastMarkerValue < markerValue) {
328                         throw new IllegalStateException("marker value of same variable must be "
329                                 + "descendant order");
330                     }
331                     if (currentValue == IntProperty.UNKNOWN_AFTER) {
332                         // Implies lastValue is less than lastMarkerValue and lastValue is not
333                         // UNKNWON_AFTER.  Estimates based on distance of two variables is screen
334                         // size.
335                         fraction = (float) (lastMarkerValue - lastValue)
336                                 / source.getMaxValue();
337                         return getFractionWithWeightAdjusted(fraction, i);
338                     } else if (currentValue >= markerValue) {
339                         if (lastIndex == index) {
340                             // same variable index,  same UI element at two different MarkerValues,
341                             // e.g. UI element moves from lastMarkerValue=500 to markerValue=0,
342                             // fraction moves from 0 to 1.
343                             fraction = (float) (lastMarkerValue - currentValue)
344                                     / (lastMarkerValue - markerValue);
345                         } else if (lastValue != IntProperty.UNKNOWN_BEFORE) {
346                             // e.g. UIElement_1 at 300 scroll to UIElement_2 at 400, figure out when
347                             // UIElement_1 is at markerValue=300,  markerValue of UIElement_2 by
348                             // adding delta of values to markerValue of UIElement_2.
349                             lastMarkerValue = lastMarkerValue + (currentValue - lastValue);
350                             fraction = (float) (lastMarkerValue - currentValue)
351                                     / (lastMarkerValue - markerValue);
352                         } else {
353                             // Last variable is UNKNOWN_BEFORE.  Estimates based on assumption total
354                             // travel distance from last variable to this variable is screen visible
355                             // size.
356                             fraction = 1f - (float) (currentValue - markerValue)
357                                     / source.getMaxValue();
358                         }
359                         return getFractionWithWeightAdjusted(fraction, i);
360                     }
361                 }
362                 lastValue = currentValue;
363                 lastIndex = index;
364                 lastMarkerValue = markerValue;
365             }
366             return 1f;
367         }
368     }
369 
370     /**
371      * Implementation of {@link ParallaxEffect} for float type.
372      */
373     static final class FloatEffect extends ParallaxEffect {
374 
375         @Override
calculateDirectValue(Parallax source)376         Number calculateDirectValue(Parallax source) {
377             if (mMarkerValues.size() != 2) {
378                 throw new RuntimeException("Must use two marker values for direct mapping");
379             }
380             if (mMarkerValues.get(0).getProperty() != mMarkerValues.get(1).getProperty()) {
381                 throw new RuntimeException(
382                         "Marker value must use same Property for direct mapping");
383             }
384             float value1 = ((FloatPropertyMarkerValue) mMarkerValues.get(0))
385                     .getMarkerValue(source);
386             float value2 = ((FloatPropertyMarkerValue) mMarkerValues.get(1))
387                     .getMarkerValue(source);
388             if (value1 > value2) {
389                 float swapValue = value2;
390                 value2 = value1;
391                 value1 = swapValue;
392             }
393 
394             Number currentValue = ((FloatProperty) mMarkerValues.get(0).getProperty()).get(source);
395             if (currentValue.floatValue() < value1) {
396                 currentValue = value1;
397             } else if (currentValue.floatValue() > value2) {
398                 currentValue = value2;
399             }
400             return currentValue;
401         }
402 
403         @Override
calculateFraction(Parallax source)404         float calculateFraction(Parallax source) {
405             int lastIndex = 0;
406             float lastValue = 0;
407             float lastMarkerValue = 0;
408             // go through all markerValues, find first markerValue that current value is less than.
409             for (int i = 0; i <  mMarkerValues.size(); i++) {
410                 FloatPropertyMarkerValue k = (FloatPropertyMarkerValue) mMarkerValues.get(i);
411                 int index = k.getProperty().getIndex();
412                 float markerValue = k.getMarkerValue(source);
413                 float currentValue = source.getFloatPropertyValue(index);
414 
415                 float fraction;
416                 if (i == 0) {
417                     if (currentValue >= markerValue) {
418                         return 0f;
419                     }
420                 } else {
421                     if (lastIndex == index && lastMarkerValue < markerValue) {
422                         throw new IllegalStateException("marker value of same variable must be "
423                                 + "descendant order");
424                     }
425                     if (currentValue == FloatProperty.UNKNOWN_AFTER) {
426                         // Implies lastValue is less than lastMarkerValue and lastValue is not
427                         // UNKNOWN_AFTER.  Estimates based on distance of two variables is screen
428                         // size.
429                         fraction = (float) (lastMarkerValue - lastValue)
430                                 / source.getMaxValue();
431                         return getFractionWithWeightAdjusted(fraction, i);
432                     } else if (currentValue >= markerValue) {
433                         if (lastIndex == index) {
434                             // same variable index,  same UI element at two different MarkerValues,
435                             // e.g. UI element moves from lastMarkerValue=500 to markerValue=0,
436                             // fraction moves from 0 to 1.
437                             fraction = (float) (lastMarkerValue - currentValue)
438                                     / (lastMarkerValue - markerValue);
439                         } else if (lastValue != FloatProperty.UNKNOWN_BEFORE) {
440                             // e.g. UIElement_1 at 300 scroll to UIElement_2 at 400, figure out when
441                             // UIElement_1 is at markerValue=300,  markerValue of UIElement_2 by
442                             // adding delta of values to markerValue of UIElement_2.
443                             lastMarkerValue = lastMarkerValue + (currentValue - lastValue);
444                             fraction = (float) (lastMarkerValue - currentValue)
445                                     / (lastMarkerValue - markerValue);
446                         } else {
447                             // Last variable is UNKNOWN_BEFORE.  Estimates based on assumption total
448                             // travel distance from last variable to this variable is screen visible
449                             // size.
450                             fraction = 1f - (float) (currentValue - markerValue)
451                                     / source.getMaxValue();
452                         }
453                         return getFractionWithWeightAdjusted(fraction, i);
454                     }
455                 }
456                 lastValue = currentValue;
457                 lastIndex = index;
458                 lastMarkerValue = markerValue;
459             }
460             return 1f;
461         }
462     }
463 
464 }
465 
466