/** * Copyright (c) 2016-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.keyframes.model.keyframedmodels; import android.util.SparseArray; import android.view.animation.Interpolator; import com.facebook.keyframes.model.HasKeyFrame; import java.util.List; /** * A generic object which holds key framed data and how to process information given a frame * progress. This object contains a generic method to apply properties of this object to a * modifyable object M (e.g. Path / Matrix / Generic Container) given a frame progress. * @param <T> The type of animated object held by this KeyFramedObject * @param <M> A modifiable object passed into the apply function to */ public abstract class KeyFramedObject<T extends HasKeyFrame, M> { private final SparseArray<T> mObjects; private final List<Interpolator> mInterpolators; private final int mFirstDescribedFrame; private final int mLastDescribedFrame; public KeyFramedObject(List<T> objects, float[][][] timingCurves) { int listSize = objects.size(); mObjects = new SparseArray<>(listSize); T object; for (int i = 0; i < listSize; i++) { object = objects.get(i); mObjects.put(object.getKeyFrame(), object); } mFirstDescribedFrame = mObjects.keyAt(0); mLastDescribedFrame = mObjects.keyAt(listSize - 1); mInterpolators = KeyFrameAnimationHelper.buildInterpolatorList(timingCurves); } /** * Constructor for creating empty/invalid KeyFramedObjects. */ protected KeyFramedObject() { mObjects = null; mInterpolators = null; mFirstDescribedFrame = 0; mLastDescribedFrame = 0; } /** * Applies the corresponding values determined by frameProgress to the modifiable object. This * method does some simple calculations to determine which key frames to use, as well as the * interpolation value between them based on the timing curve information, then delegates to * {@link #applyImpl(HasKeyFrame, HasKeyFrame, float, Object)} for the actual application to * the modifiable object. * @param frameProgress The progress, described in frames, of the animation. * @param modifiable The object to insert values into. */ public void apply(float frameProgress, M modifiable) { if (mInterpolators.isEmpty() || frameProgress <= mFirstDescribedFrame) { applyImpl(mObjects.get(mFirstDescribedFrame), null, 0, modifiable); return; } if (frameProgress >= mLastDescribedFrame) { applyImpl(mObjects.get(mLastDescribedFrame), null, 0, modifiable); return; } T thisFrame = null; T nextFrame = null; int interpolatorIndex; int len = mInterpolators.size(); for (interpolatorIndex = 0; interpolatorIndex < len; interpolatorIndex++) { if (mObjects.keyAt(interpolatorIndex) == frameProgress || (mObjects.keyAt(interpolatorIndex) < frameProgress && mObjects.keyAt(interpolatorIndex + 1) > frameProgress)) { thisFrame = mObjects.valueAt(interpolatorIndex); nextFrame = mObjects.valueAt(interpolatorIndex + 1); break; } } float progress = (frameProgress - thisFrame.getKeyFrame()) / (nextFrame.getKeyFrame() - thisFrame.getKeyFrame()); applyImpl( thisFrame, nextFrame, mInterpolators.get(interpolatorIndex).getInterpolation(progress), modifiable); } /** * Apply the given state to a modifiable. * @param stateA Initial state * @param stateB End state * @param interpolationValue Progress [0..1] between stateA and stateB * @param modifiable The modifiable object to apply the values to */ protected abstract void applyImpl( T stateA, T stateB, float interpolationValue, M modifiable); /** * Given two values and the progress from valueA to valueB, returns the transitional value in * between. */ protected static float interpolateValue(float valueA, float valueB, float progress) { return valueA + (valueB - valueA) * progress; } }