blob: 5760bdb1108aaaca17eab3d48d994e8991db6ac1 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v17.leanback.widget;
import android.animation.PropertyValuesHolder;
import android.support.v17.leanback.widget.Parallax.FloatProperty;
import android.support.v17.leanback.widget.Parallax.FloatPropertyMarkerValue;
import android.support.v17.leanback.widget.Parallax.IntProperty;
import android.support.v17.leanback.widget.Parallax.PropertyMarkerValue;
import java.util.ArrayList;
import java.util.List;
/**
* ParallaxEffect class drives changes in {@link ParallaxTarget} in response to changes in
* variables defined in {@link Parallax}.
* <p>
* ParallaxEffect has a list of {@link Parallax.PropertyMarkerValue}s which represents the range of
* values that source variables can take. The main function is
* {@link ParallaxEffect#performMapping(Parallax)} which computes a fraction between 0 and 1
* based on the current values of variables in {@link Parallax}. As the parallax effect goes
* on, the fraction increases from 0 at beginning to 1 at the end. Then the fraction is passed on
* to {@link ParallaxTarget#update(float)}.
* <p>
* ParallaxEffect has two concrete subclasses, {@link IntEffect} and {@link FloatEffect}.
*/
public abstract class ParallaxEffect<ParallaxEffectT extends ParallaxEffect,
PropertyMarkerValueT extends Parallax.PropertyMarkerValue> {
final List<PropertyMarkerValueT> mMarkerValues = new ArrayList<PropertyMarkerValueT>(2);
final List<Float> mWeights = new ArrayList<Float>(2);
final List<Float> mTotalWeights = new ArrayList<Float>(2);
final List<ParallaxTarget> mTargets = new ArrayList<ParallaxTarget>(4);
/**
* Returns the list of {@link PropertyMarkerValue}s, which represents the range of values that
* source variables can take.
*
* @return A list of {@link Parallax.PropertyMarkerValue}s.
* @see #performMapping(Parallax)
*/
public final List<PropertyMarkerValueT> getPropertyRanges() {
return mMarkerValues;
}
/**
* Returns a list of Float objects that represents weight associated with each variable range.
* Weights are used when there are three or more marker values.
*
* @return A list of Float objects that represents weight associated with each variable range.
* @hide
*/
public final List<Float> getWeights() {
return mWeights;
}
/**
* Sets the list of {@link PropertyMarkerValue}s, which represents the range of values that
* source variables can take.
*
* @param markerValues A list of {@link PropertyMarkerValue}s.
* @see #performMapping(Parallax)
*/
public final void setPropertyRanges(PropertyMarkerValueT... markerValues) {
mMarkerValues.clear();
for (PropertyMarkerValueT markerValue : markerValues) {
mMarkerValues.add(markerValue);
}
}
/**
* Sets a list of Float objects that represents weight associated with each variable range.
* Weights are used when there are three or more marker values.
*
* @param weights A list of Float objects that represents weight associated with each variable
* range.
* @hide
*/
public final void setWeights(float... weights) {
for (float weight : weights) {
if (weight <= 0) {
throw new IllegalArgumentException();
}
}
mWeights.clear();
mTotalWeights.clear();
float totalWeight = 0f;
for (float weight : weights) {
mWeights.add(weight);
totalWeight += weight;
mTotalWeights.add(totalWeight);
}
}
/**
* Sets a list of Float objects that represents weight associated with each variable range.
* Weights are used when there are three or more marker values.
*
* @param weights A list of Float objects that represents weight associated with each variable
* range.
* @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
* @hide
*/
public final ParallaxEffect weights(float... weights) {
setWeights(weights);
return this;
}
/**
* Add a ParallaxTarget to run parallax effect.
*
* @param target ParallaxTarget to add.
*/
public final void addTarget(ParallaxTarget target) {
mTargets.add(target);
}
/**
* Add a ParallaxTarget to run parallax effect.
*
* @param target ParallaxTarget to add.
* @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
*/
public final ParallaxEffect target(ParallaxTarget target) {
mTargets.add(target);
return this;
}
/**
* Creates a {@link ParallaxTarget} from {@link PropertyValuesHolder} and adds it to the list
* of targets.
*
* @param targetObject Target object for PropertyValuesHolderTarget.
* @param values PropertyValuesHolder for PropertyValuesHolderTarget.
* @return This ParallaxEffect object, allowing calls to methods in this class to be chained.
*/
public final ParallaxEffect target(Object targetObject, PropertyValuesHolder values) {
mTargets.add(new ParallaxTarget.PropertyValuesHolderTarget(targetObject, values));
return this;
}
/**
* Returns the list of {@link ParallaxTarget} objects.
*
* @return The list of {@link ParallaxTarget} objects.
*/
public final List<ParallaxTarget> getTargets() {
return mTargets;
}
/**
* Remove a {@link ParallaxTarget} object from the list.
* @param target The {@link ParallaxTarget} object to be removed.
*/
public final void removeTarget(ParallaxTarget target) {
mTargets.remove(target);
}
/**
* Perform mapping from {@link Parallax} to list of {@link ParallaxTarget}.
*/
public final void performMapping(Parallax source) {
if (mMarkerValues.size() < 2) {
return;
}
source.verifyProperties();
float fraction = calculateFraction(source);
for (int i = 0; i < mTargets.size(); i++) {
mTargets.get(i).update(fraction);
}
}
/**
* This method is expected to compute a fraction between 0 and 1 based on the current values of
* variables in {@link Parallax}. As the parallax effect goes on, the fraction increases
* from 0 at beginning to 1 at the end.
*
* @return Float value between 0 and 1.
*/
protected abstract float calculateFraction(Parallax source);
/**
* When there are multiple ranges (aka three or more markerValues), this method adjust the
* fraction inside a range to fraction of whole range.
* e.g. four marker values, three weight values: 6, 2, 2. totalWeights are 6, 8, 10
* When markerValueIndex is 3, the fraction is inside last range.
* adjusted_fraction = 8 / 10 + 2 / 10 * fraction.
*/
final float getFractionWithWeightAdjusted(float fraction, int markerValueIndex) {
// when there are three or more markerValues, take weight into consideration.
if (mMarkerValues.size() >= 3) {
final boolean hasWeightsDefined = mWeights.size() == mMarkerValues.size() - 1;
if (hasWeightsDefined) {
// use weights user defined
final float allWeights = mTotalWeights.get(mTotalWeights.size() - 1);
fraction = fraction * mWeights.get(markerValueIndex - 1) / allWeights;
if (markerValueIndex >= 2) {
fraction += mTotalWeights.get(markerValueIndex - 2) / allWeights;
}
} else {
// assume each range has same weight.
final float allWeights = mMarkerValues.size() - 1;
fraction = fraction / allWeights;
if (markerValueIndex >= 2) {
fraction += (float) (markerValueIndex - 1) / allWeights;
}
}
}
return fraction;
}
/**
* Implementation of {@link ParallaxEffect} for integer type.
*/
public static final class IntEffect extends ParallaxEffect<IntEffect,
Parallax.IntPropertyMarkerValue> {
@Override
protected float calculateFraction(Parallax s) {
Parallax.IntParallax source = (Parallax.IntParallax) s;
int lastIndex = 0;
int lastValue = 0;
int lastMarkerValue = 0;
// go through all markerValues, find first markerValue that current value is less than.
for (int i = 0; i < mMarkerValues.size(); i++) {
Parallax.IntPropertyMarkerValue k = mMarkerValues.get(i);
int index = k.getProperty().getIndex();
int markerValue = k.getMarkerValue(source);
int currentValue = source.getPropertyValue(index);
float fraction;
if (i == 0) {
if (currentValue >= markerValue) {
return 0f;
}
} else {
if (lastIndex == index && lastMarkerValue < markerValue) {
throw new IllegalStateException("marker value of same variable must be "
+ "descendant order");
}
if (currentValue == IntProperty.UNKNOWN_AFTER) {
// Implies lastValue is less than lastMarkerValue and lastValue is not
// UNKNWON_AFTER. Estimates based on distance of two variables is screen
// size.
fraction = (float) (lastMarkerValue - lastValue)
/ source.getMaxValue();
return getFractionWithWeightAdjusted(fraction, i);
} else if (currentValue >= markerValue) {
if (lastIndex == index) {
// same variable index, same UI element at two different MarkerValues,
// e.g. UI element moves from lastMarkerValue=500 to markerValue=0,
// fraction moves from 0 to 1.
fraction = (float) (lastMarkerValue - currentValue)
/ (lastMarkerValue - markerValue);
} else if (lastValue != IntProperty.UNKNOWN_BEFORE) {
// e.g. UIElement_1 at 300 scroll to UIElement_2 at 400, figure out when
// UIElement_1 is at markerValue=300, markerValue of UIElement_2 by
// adding delta of values to markerValue of UIElement_2.
lastMarkerValue = lastMarkerValue + (currentValue - lastValue);
fraction = (float) (lastMarkerValue - currentValue)
/ (lastMarkerValue - markerValue);
} else {
// Last variable is UNKNOWN_BEFORE. Estimates based on assumption total
// travel distance from last variable to this variable is screen visible
// size.
fraction = 1f - (float) (currentValue - markerValue)
/ source.getMaxValue();
}
return getFractionWithWeightAdjusted(fraction, i);
}
}
lastValue = currentValue;
lastIndex = index;
lastMarkerValue = markerValue;
}
return 1f;
}
}
/**
* Implementation of {@link ParallaxEffect} for float type.
*/
public static final class FloatEffect extends ParallaxEffect<FloatEffect,
Parallax.FloatPropertyMarkerValue> {
@Override
protected float calculateFraction(Parallax s) {
Parallax.FloatParallax source = (Parallax.FloatParallax) s;
int lastIndex = 0;
float lastValue = 0;
float lastMarkerValue = 0;
// go through all markerValues, find first markerValue that current value is less than.
for (int i = 0; i < mMarkerValues.size(); i++) {
FloatPropertyMarkerValue k = mMarkerValues.get(i);
int index = k.getProperty().getIndex();
float markerValue = k.getMarkerValue(source);
float currentValue = source.getPropertyValue(index);
float fraction;
if (i == 0) {
if (currentValue >= markerValue) {
return 0f;
}
} else {
if (lastIndex == index && lastMarkerValue < markerValue) {
throw new IllegalStateException("marker value of same variable must be "
+ "descendant order");
}
if (currentValue == FloatProperty.UNKNOWN_AFTER) {
// Implies lastValue is less than lastMarkerValue and lastValue is not
// UNKNOWN_AFTER. Estimates based on distance of two variables is screen
// size.
fraction = (float) (lastMarkerValue - lastValue)
/ source.getMaxValue();
return getFractionWithWeightAdjusted(fraction, i);
} else if (currentValue >= markerValue) {
if (lastIndex == index) {
// same variable index, same UI element at two different MarkerValues,
// e.g. UI element moves from lastMarkerValue=500 to markerValue=0,
// fraction moves from 0 to 1.
fraction = (float) (lastMarkerValue - currentValue)
/ (lastMarkerValue - markerValue);
} else if (lastValue != FloatProperty.UNKNOWN_BEFORE) {
// e.g. UIElement_1 at 300 scroll to UIElement_2 at 400, figure out when
// UIElement_1 is at markerValue=300, markerValue of UIElement_2 by
// adding delta of values to markerValue of UIElement_2.
lastMarkerValue = lastMarkerValue + (currentValue - lastValue);
fraction = (float) (lastMarkerValue - currentValue)
/ (lastMarkerValue - markerValue);
} else {
// Last variable is UNKNOWN_BEFORE. Estimates based on assumption total
// travel distance from last variable to this variable is screen visible
// size.
fraction = 1f - (float) (currentValue - markerValue)
/ source.getMaxValue();
}
return getFractionWithWeightAdjusted(fraction, i);
}
}
lastValue = currentValue;
lastIndex = index;
lastMarkerValue = markerValue;
}
return 1f;
}
}
}