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