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 * @paramClass 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