• 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.support.annotation.CallSuper;
20 import android.support.v17.leanback.widget.ParallaxEffect.FloatEffect;
21 import android.support.v17.leanback.widget.ParallaxEffect.IntEffect;
22 import android.util.Property;
23 
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 
28 /**
29  * Parallax tracks a list of dynamic {@link Property}s typically representing foreground UI
30  * element positions on screen. Parallax keeps a list of {@link ParallaxEffect} objects which define
31  * rules to mapping property values to {@link ParallaxTarget}.
32  *
33  * <p>
34  * Example:
35  * <code>
36  *     // when Property "var1" changes from 15 to max value, perform parallax effect to
37  *     // change myView's translationY from 0 to 100.
38  *     Parallax<IntProperty> parallax = new Parallax<IntProperty>() {...};
39  *     p1 = parallax.addProperty("var1");
40  *     parallax.addEffect(p1.at(15), p1.atMax())
41  *             .target(myView, PropertyValuesHolder.ofFloat("translationY", 0, 100));
42  * </code>
43  * </p>
44  *
45  * <p>
46  * To create a {@link ParallaxEffect}, user calls {@link #addEffect(PropertyMarkerValue[])} with a
47  * list of {@link PropertyMarkerValue} which defines the range of {@link Parallax.IntProperty} or
48  * {@link Parallax.FloatProperty}. Then user adds {@link ParallaxTarget} into
49  * {@link ParallaxEffect}.
50  * </p>
51  * <p>
52  * App may subclass {@link Parallax.IntProperty} or {@link Parallax.FloatProperty} to supply
53  * additional information about how to retrieve Property value.  {@link RecyclerViewParallax} is
54  * a great example of Parallax implementation tracking child view positions on screen.
55  * </p>
56  * <p>
57  * <ul>Restrictions of properties
58  * <li>FloatProperty and IntProperty cannot be mixed in one Parallax</li>
59  * <li>Values must be in ascending order.</li>
60  * <li>If the UI element is unknown above screen, use UNKNOWN_BEFORE.</li>
61  * <li>if the UI element is unknown below screen, use UNKNOWN_AFTER.</li>
62  * <li>UNKNOWN_BEFORE and UNKNOWN_AFTER are not allowed to be next to each other.</li>
63  * </ul>
64  * These rules will be verified at runtime.
65  * </p>
66  * <p>
67  * Subclass must override {@link #updateValues()} to update property values and perform
68  * {@link ParallaxEffect}s. Subclass may call {@link #updateValues()} automatically e.g.
69  * {@link RecyclerViewParallax} calls {@link #updateValues()} in RecyclerView scrolling. App might
70  * call {@link #updateValues()} manually when Parallax is unaware of the value change. For example,
71  * when a slide transition is running, {@link RecyclerViewParallax} is unaware of translation value
72  * changes; it's the app's responsibility to call {@link #updateValues()} in every frame of
73  * animation.
74  * </p>
75  * @param <PropertyT> Subclass of {@link Parallax.IntProperty} or {@link Parallax.FloatProperty}
76  */
77 public abstract class Parallax<PropertyT extends android.util.Property> {
78 
79     /**
80      * Class holding a fixed value for a Property in {@link Parallax}.
81      * @param <PropertyT> Class of the property, e.g. {@link IntProperty} or {@link FloatProperty}.
82      */
83     public static class PropertyMarkerValue<PropertyT> {
84         private final PropertyT mProperty;
85 
PropertyMarkerValue(PropertyT property)86         public PropertyMarkerValue(PropertyT property) {
87             mProperty = property;
88         }
89 
90         /**
91          * @return Associated property.
92          */
getProperty()93         public PropertyT getProperty() {
94             return mProperty;
95         }
96     }
97 
98     /**
99      * IntProperty provide access to an index based integer type property inside
100      * {@link Parallax}. The IntProperty typically represents UI element position inside
101      * {@link Parallax}.
102      */
103     public static class IntProperty extends Property<Parallax, Integer> {
104 
105         /**
106          * Property value is unknown and it's smaller than minimal value of Parallax. For
107          * example if a child is not created and before the first visible child of RecyclerView.
108          */
109         public static final int UNKNOWN_BEFORE = Integer.MIN_VALUE;
110 
111         /**
112          * Property value is unknown and it's larger than {@link Parallax#getMaxValue()}. For
113          * example if a child is not created and after the last visible child of RecyclerView.
114          */
115         public static final int UNKNOWN_AFTER = Integer.MAX_VALUE;
116 
117         private final int mIndex;
118 
119         /**
120          * Constructor.
121          *
122          * @param name Name of this Property.
123          * @param index Index of this Property inside {@link Parallax}.
124          */
IntProperty(String name, int index)125         public IntProperty(String name, int index) {
126             super(Integer.class, name);
127             mIndex = index;
128         }
129 
130         @Override
get(Parallax object)131         public final Integer get(Parallax object) {
132             return object.getIntPropertyValue(mIndex);
133         }
134 
135         @Override
set(Parallax object, Integer value)136         public final void set(Parallax object, Integer value) {
137             object.setIntPropertyValue(mIndex, value);
138         }
139 
140         /**
141          * @return Index of this Property in {@link Parallax}.
142          */
getIndex()143         public final int getIndex() {
144             return mIndex;
145         }
146 
147         /**
148          * Fast version of get() method that returns a primitive int value of the Property.
149          * @param object The Parallax object that owns this Property.
150          * @return Int value of the Property.
151          */
getValue(Parallax object)152         public final int getValue(Parallax object) {
153             return object.getIntPropertyValue(mIndex);
154         }
155 
156         /**
157          * Fast version of set() method that takes a primitive int value into the Property.
158          *
159          * @param object The Parallax object that owns this Property.
160          * @param value Int value of the Property.
161          */
setValue(Parallax object, int value)162         public final void setValue(Parallax object, int value) {
163             object.setIntPropertyValue(mIndex, value);
164         }
165 
166         /**
167          * Creates an {@link PropertyMarkerValue} object for the absolute marker value.
168          *
169          * @param absoluteValue The integer marker value.
170          * @return A new {@link PropertyMarkerValue} object.
171          */
atAbsolute(int absoluteValue)172         public final PropertyMarkerValue atAbsolute(int absoluteValue) {
173             return new IntPropertyMarkerValue(this, absoluteValue, 0f);
174         }
175 
176         /**
177          * Creates an {@link PropertyMarkerValue} object for the marker value representing
178          * {@link Parallax#getMaxValue()}.
179          *
180          * @return A new {@link PropertyMarkerValue} object.
181          */
atMax()182         public final PropertyMarkerValue atMax() {
183             return new IntPropertyMarkerValue(this, 0, 1f);
184         }
185 
186         /**
187          * Creates an {@link PropertyMarkerValue} object for the marker value representing 0.
188          *
189          * @return A new {@link PropertyMarkerValue} object.
190          */
atMin()191         public final PropertyMarkerValue atMin() {
192             return new IntPropertyMarkerValue(this, 0);
193         }
194 
195         /**
196          * Creates an {@link PropertyMarkerValue} object for a fraction of
197          * {@link Parallax#getMaxValue()}.
198          *
199          * @param fractionOfMaxValue 0 to 1 fraction to multiply with
200          *                                       {@link Parallax#getMaxValue()} for
201          *                                       the marker value.
202          * @return A new {@link PropertyMarkerValue} object.
203          */
atFraction(float fractionOfMaxValue)204         public final PropertyMarkerValue atFraction(float fractionOfMaxValue) {
205             return new IntPropertyMarkerValue(this, 0, fractionOfMaxValue);
206         }
207 
208         /**
209          * Create an {@link PropertyMarkerValue} object by multiplying the fraction with
210          * {@link Parallax#getMaxValue()} and adding offsetValue to it.
211          *
212          * @param offsetValue                    An offset integer value to be added to marker
213          *                                       value.
214          * @param fractionOfMaxParentVisibleSize 0 to 1 fraction to multiply with
215          *                                       {@link Parallax#getMaxValue()} for
216          *                                       the marker value.
217          * @return A new {@link PropertyMarkerValue} object.
218          */
at(int offsetValue, float fractionOfMaxParentVisibleSize)219         public final PropertyMarkerValue at(int offsetValue,
220                 float fractionOfMaxParentVisibleSize) {
221             return new IntPropertyMarkerValue(this, offsetValue, fractionOfMaxParentVisibleSize);
222         }
223     }
224 
225     /**
226      * Implementation of {@link PropertyMarkerValue} for {@link IntProperty}.
227      */
228     static class IntPropertyMarkerValue extends PropertyMarkerValue<IntProperty> {
229         private final int mValue;
230         private final float mFactionOfMax;
231 
IntPropertyMarkerValue(IntProperty property, int value)232         IntPropertyMarkerValue(IntProperty property, int value) {
233             this(property, value, 0f);
234         }
235 
IntPropertyMarkerValue(IntProperty property, int value, float fractionOfMax)236         IntPropertyMarkerValue(IntProperty property, int value, float fractionOfMax) {
237             super(property);
238             mValue = value;
239             mFactionOfMax = fractionOfMax;
240         }
241 
242         /**
243          * @return The marker value of integer type.
244          */
getMarkerValue(Parallax source)245         final int getMarkerValue(Parallax source) {
246             return mFactionOfMax == 0 ? mValue : mValue + Math.round(source
247                     .getMaxValue() * mFactionOfMax);
248         }
249     }
250 
251     /**
252      * FloatProperty provide access to an index based integer type property inside
253      * {@link Parallax}. The FloatProperty typically represents UI element position inside
254      * {@link Parallax}.
255      */
256     public static class FloatProperty extends Property<Parallax, Float> {
257 
258         /**
259          * Property value is unknown and it's smaller than minimal value of Parallax. For
260          * example if a child is not created and before the first visible child of RecyclerView.
261          */
262         public static final float UNKNOWN_BEFORE = -Float.MAX_VALUE;
263 
264         /**
265          * Property value is unknown and it's larger than {@link Parallax#getMaxValue()}. For
266          * example if a child is not created and after the last visible child of RecyclerView.
267          */
268         public static final float UNKNOWN_AFTER = Float.MAX_VALUE;
269 
270         private final int mIndex;
271 
272         /**
273          * Constructor.
274          *
275          * @param name Name of this Property.
276          * @param index Index of this Property inside {@link Parallax}.
277          */
FloatProperty(String name, int index)278         public FloatProperty(String name, int index) {
279             super(Float.class, name);
280             mIndex = index;
281         }
282 
283         @Override
get(Parallax object)284         public final Float get(Parallax object) {
285             return object.getFloatPropertyValue(mIndex);
286         }
287 
288         @Override
set(Parallax object, Float value)289         public final void set(Parallax object, Float value) {
290             object.setFloatPropertyValue(mIndex, value);
291         }
292 
293         /**
294          * @return Index of this Property in {@link Parallax}.
295          */
getIndex()296         public final int getIndex() {
297             return mIndex;
298         }
299 
300         /**
301          * Fast version of get() method that returns a primitive int value of the Property.
302          * @param object The Parallax object that owns this Property.
303          * @return Float value of the Property.
304          */
getValue(Parallax object)305         public final float getValue(Parallax object) {
306             return object.getFloatPropertyValue(mIndex);
307         }
308 
309         /**
310          * Fast version of set() method that takes a primitive float value into the Property.
311          *
312          * @param object The Parallax object that owns this Property.
313          * @param value Float value of the Property.
314          */
setValue(Parallax object, float value)315         public final void setValue(Parallax object, float value) {
316             object.setFloatPropertyValue(mIndex, value);
317         }
318 
319         /**
320          * Creates an {@link PropertyMarkerValue} object for the absolute marker value.
321          *
322          * @param markerValue The float marker value.
323          * @return A new {@link PropertyMarkerValue} object.
324          */
atAbsolute(float markerValue)325         public final PropertyMarkerValue atAbsolute(float markerValue) {
326             return new FloatPropertyMarkerValue(this, markerValue, 0f);
327         }
328 
329         /**
330          * Creates an {@link PropertyMarkerValue} object for the marker value representing
331          * {@link Parallax#getMaxValue()}.
332          *
333          * @return A new {@link PropertyMarkerValue} object.
334          */
atMax()335         public final PropertyMarkerValue atMax() {
336             return new FloatPropertyMarkerValue(this, 0, 1f);
337         }
338 
339         /**
340          * Creates an {@link PropertyMarkerValue} object for the marker value representing 0.
341          *
342          * @return A new {@link PropertyMarkerValue} object.
343          */
atMin()344         public final PropertyMarkerValue atMin() {
345             return new FloatPropertyMarkerValue(this, 0);
346         }
347 
348         /**
349          * Creates an {@link PropertyMarkerValue} object for a fraction of
350          * {@link Parallax#getMaxValue()}.
351          *
352          * @param fractionOfMaxParentVisibleSize 0 to 1 fraction to multiply with
353          *                                       {@link Parallax#getMaxValue()} for
354          *                                       the marker value.
355          * @return A new {@link PropertyMarkerValue} object.
356          */
atFraction(float fractionOfMaxParentVisibleSize)357         public final PropertyMarkerValue atFraction(float fractionOfMaxParentVisibleSize) {
358             return new FloatPropertyMarkerValue(this, 0, fractionOfMaxParentVisibleSize);
359         }
360 
361         /**
362          * Create an {@link PropertyMarkerValue} object by multiplying the fraction with
363          * {@link Parallax#getMaxValue()} and adding offsetValue to it.
364          *
365          * @param offsetValue                    An offset float value to be added to marker value.
366          * @param fractionOfMaxParentVisibleSize 0 to 1 fraction to multiply with
367          *                                       {@link Parallax#getMaxValue()} for
368          *                                       the marker value.
369          * @return A new {@link PropertyMarkerValue} object.
370          */
at(float offsetValue, float fractionOfMaxParentVisibleSize)371         public final PropertyMarkerValue at(float offsetValue,
372                 float fractionOfMaxParentVisibleSize) {
373             return new FloatPropertyMarkerValue(this, offsetValue, fractionOfMaxParentVisibleSize);
374         }
375     }
376 
377     /**
378      * Implementation of {@link PropertyMarkerValue} for {@link FloatProperty}.
379      */
380     static class FloatPropertyMarkerValue extends PropertyMarkerValue<FloatProperty> {
381         private final float mValue;
382         private final float mFactionOfMax;
383 
FloatPropertyMarkerValue(FloatProperty property, float value)384         FloatPropertyMarkerValue(FloatProperty property, float value) {
385             this(property, value, 0f);
386         }
387 
FloatPropertyMarkerValue(FloatProperty property, float value, float fractionOfMax)388         FloatPropertyMarkerValue(FloatProperty property, float value, float fractionOfMax) {
389             super(property);
390             mValue = value;
391             mFactionOfMax = fractionOfMax;
392         }
393 
394         /**
395          * @return The marker value.
396          */
getMarkerValue(Parallax source)397         final float getMarkerValue(Parallax source) {
398             return mFactionOfMax == 0 ? mValue : mValue + source.getMaxValue()
399                     * mFactionOfMax;
400         }
401     }
402 
403     final List<PropertyT> mProperties = new ArrayList<PropertyT>();
404     final List<PropertyT> mPropertiesReadOnly = Collections.unmodifiableList(mProperties);
405 
406     private int[] mValues = new int[4];
407     private float[] mFloatValues = new float[4];
408 
409     private final List<ParallaxEffect> mEffects = new ArrayList<ParallaxEffect>(4);
410 
411     /**
412      * Return the max value which is typically size of parent visible area, e.g. RecyclerView's
413      * height if we are tracking Y position of a child. The size can be used to calculate marker
414      * value using the provided fraction of FloatPropertyMarkerValue.
415      *
416      * @return Size of parent visible area.
417      * @see IntPropertyMarkerValue#IntPropertyMarkerValue(IntProperty, int, float)
418      * @see FloatPropertyMarkerValue#FloatPropertyMarkerValue(FloatProperty, float, float)
419      */
getMaxValue()420     public abstract float getMaxValue();
421 
422     /**
423      * Get index based property value.
424      *
425      * @param index Index of the property.
426      * @return Value of the property.
427      */
getIntPropertyValue(int index)428     final int getIntPropertyValue(int index) {
429         return mValues[index];
430     }
431 
432     /**
433      * Set index based property value.
434      *
435      * @param index Index of the property.
436      * @param value Value of the property.
437      */
setIntPropertyValue(int index, int value)438     final void setIntPropertyValue(int index, int value) {
439         if (index >= mProperties.size()) {
440             throw new ArrayIndexOutOfBoundsException();
441         }
442         mValues[index] = value;
443     }
444 
445     /**
446      * Add a new IntProperty in the Parallax object. App may override
447      * {@link #createProperty(String, int)}.
448      *
449      * @param name Name of the property.
450      * @return Newly created Property object.
451      * @see #createProperty(String, int)
452      */
addProperty(String name)453     public final PropertyT addProperty(String name) {
454         int newPropertyIndex = mProperties.size();
455         PropertyT property = createProperty(name, newPropertyIndex);
456         if (property instanceof IntProperty) {
457             int size = mValues.length;
458             if (size == newPropertyIndex) {
459                 int[] newValues = new int[size * 2];
460                 for (int i = 0; i < size; i++) {
461                     newValues[i] = mValues[i];
462                 }
463                 mValues = newValues;
464             }
465             mValues[newPropertyIndex] = IntProperty.UNKNOWN_AFTER;
466         } else if (property instanceof FloatProperty) {
467             int size = mFloatValues.length;
468             if (size == newPropertyIndex) {
469                 float[] newValues = new float[size * 2];
470                 for (int i = 0; i < size; i++) {
471                     newValues[i] = mFloatValues[i];
472                 }
473                 mFloatValues = newValues;
474             }
475             mFloatValues[newPropertyIndex] = FloatProperty.UNKNOWN_AFTER;
476         } else {
477             throw new IllegalArgumentException("Invalid Property type");
478         }
479         mProperties.add(property);
480         return property;
481     }
482 
483     /**
484      * Verify sanity of property values, throws RuntimeException if fails. The property values
485      * must be in ascending order. UNKNOW_BEFORE and UNKNOWN_AFTER are not allowed to be next to
486      * each other.
487      */
verifyIntProperties()488     void verifyIntProperties() throws IllegalStateException {
489         if (mProperties.size() < 2) {
490             return;
491         }
492         int last = getIntPropertyValue(0);
493         for (int i = 1; i < mProperties.size(); i++) {
494             int v = getIntPropertyValue(i);
495             if (v < last) {
496                 throw new IllegalStateException(String.format("Parallax Property[%d]\"%s\" is"
497                                 + " smaller than Property[%d]\"%s\"",
498                         i, mProperties.get(i).getName(),
499                         i - 1, mProperties.get(i - 1).getName()));
500             } else if (last == IntProperty.UNKNOWN_BEFORE && v == IntProperty.UNKNOWN_AFTER) {
501                 throw new IllegalStateException(String.format("Parallax Property[%d]\"%s\" is"
502                                 + " UNKNOWN_BEFORE and Property[%d]\"%s\" is UNKNOWN_AFTER",
503                         i - 1, mProperties.get(i - 1).getName(),
504                         i, mProperties.get(i).getName()));
505             }
506             last = v;
507         }
508     }
509 
verifyFloatProperties()510     final void verifyFloatProperties() throws IllegalStateException {
511         if (mProperties.size() < 2) {
512             return;
513         }
514         float last = getFloatPropertyValue(0);
515         for (int i = 1; i < mProperties.size(); i++) {
516             float v = getFloatPropertyValue(i);
517             if (v < last) {
518                 throw new IllegalStateException(String.format("Parallax Property[%d]\"%s\" is"
519                                 + " smaller than Property[%d]\"%s\"",
520                         i, mProperties.get(i).getName(),
521                         i - 1, mProperties.get(i - 1).getName()));
522             } else if (last == FloatProperty.UNKNOWN_BEFORE && v
523                     == FloatProperty.UNKNOWN_AFTER) {
524                 throw new IllegalStateException(String.format("Parallax Property[%d]\"%s\" is"
525                                 + " UNKNOWN_BEFORE and Property[%d]\"%s\" is UNKNOWN_AFTER",
526                         i - 1, mProperties.get(i - 1).getName(),
527                         i, mProperties.get(i).getName()));
528             }
529             last = v;
530         }
531     }
532 
533     /**
534      * Get index based property value.
535      *
536      * @param index Index of the property.
537      * @return Value of the property.
538      */
getFloatPropertyValue(int index)539     final float getFloatPropertyValue(int index) {
540         return mFloatValues[index];
541     }
542 
543     /**
544      * Set index based property value.
545      *
546      * @param index Index of the property.
547      * @param value Value of the property.
548      */
setFloatPropertyValue(int index, float value)549     final void setFloatPropertyValue(int index, float value) {
550         if (index >= mProperties.size()) {
551             throw new ArrayIndexOutOfBoundsException();
552         }
553         mFloatValues[index] = value;
554     }
555 
556     /**
557      * @return A unmodifiable list of properties.
558      */
getProperties()559     public final List<PropertyT> getProperties() {
560         return mPropertiesReadOnly;
561     }
562 
563     /**
564      * Create a new Property object. App does not directly call this method.  See
565      * {@link #addProperty(String)}.
566      *
567      * @param index  Index of the property in this Parallax object.
568      * @return Newly created Property object.
569      */
createProperty(String name, int index)570     public abstract PropertyT createProperty(String name, int index);
571 
572     /**
573      * Update property values and perform {@link ParallaxEffect}s. Subclass may override and call
574      * super.updateValues() after updated properties values.
575      */
576     @CallSuper
updateValues()577     public void updateValues() {
578         for (int i = 0; i < mEffects.size(); i++) {
579             mEffects.get(i).performMapping(this);
580         }
581     }
582 
583     /**
584      * Returns a list of {@link ParallaxEffect} object which defines rules to perform mapping to
585      * multiple {@link ParallaxTarget}s.
586      *
587      * @return A list of {@link ParallaxEffect} object.
588      */
getEffects()589     public List<ParallaxEffect> getEffects() {
590         return mEffects;
591     }
592 
593     /**
594      * Remove the {@link ParallaxEffect} object.
595      *
596      * @param effect The {@link ParallaxEffect} object to remove.
597      */
removeEffect(ParallaxEffect effect)598     public void removeEffect(ParallaxEffect effect) {
599         mEffects.remove(effect);
600     }
601 
602     /**
603      * Remove all {@link ParallaxEffect} objects.
604      */
removeAllEffects()605     public void removeAllEffects() {
606         mEffects.clear();
607     }
608 
609     /**
610      * Create a {@link ParallaxEffect} object that will track source variable changes within a
611      * provided set of ranges.
612      *
613      * @param ranges A list of marker values that defines the ranges.
614      * @return Newly created ParallaxEffect object.
615      */
addEffect(PropertyMarkerValue... ranges)616     public ParallaxEffect addEffect(PropertyMarkerValue... ranges) {
617         ParallaxEffect effect;
618         if (ranges[0].getProperty() instanceof IntProperty) {
619             effect = new IntEffect();
620         } else {
621             effect = new FloatEffect();
622         }
623         effect.setPropertyRanges(ranges);
624         mEffects.add(effect);
625         return effect;
626     }
627 
628 }
629