/*
* Copyright 2017-present The Material Motion Authors. All Rights Reserved.
*
* 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 com.google.android.material.motion.sources;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.support.v4.util.SimpleArrayMap;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.google.android.indefinite.observable.IndefiniteObservable.Subscription;
import com.google.android.indefinite.observable.Observer;
import com.google.android.material.motion.MotionObserver;
import com.google.android.material.motion.MotionObserver.SimpleMotionObserver;
import com.google.android.material.motion.MotionState;
import com.google.android.material.motion.Source;
import com.google.android.material.motion.interactions.Tween;
public class TweenSource<O, T> extends Source<T> {
private static final TimeInterpolator defaultInterpolator =
new AccelerateDecelerateInterpolator();
private static final Object[] lengthTwoArray = new Object[2];
private final Tween<O, T> interaction;
private final ValueAnimator animator;
private final SimpleArrayMap<Observer<T>, AnimatorUpdateListener> updateListeners =
new SimpleArrayMap<>();
private Subscription evaluatorSubscription;
private Subscription valuesSubscription;
private Subscription offsetsSubscription;
private Subscription timingFunctionsSubscription;
private Subscription durationSubscription;
private Subscription delaySubscription;
private Subscription repeatCountSubscription;
private Subscription repeatModeSubscription;
private Subscription timingFunctionSubscription;
private TypeEvaluator<T> lastEvaluator;
private Object[] lastValues;
private float[] lastOffsets;
private TimeInterpolator[] lastTimingFunctions;
private Long lastDuration;
private Long lastDelay;
private Integer lastRepeatCount;
private Integer lastRepeatMode;
private TimeInterpolator lastTimingFunction;
private boolean initialized;
public TweenSource(Tween<O, T> interaction) {
super(interaction);
this.interaction = interaction;
animator = new ValueAnimator();
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
for (int i = 0, count = updateListeners.size(); i < count; i++) {
updateListeners.valueAt(i).onAnimationUpdate(animation);
}
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
interaction.state.write(MotionState.ACTIVE);
}
@Override
public void onAnimationEnd(Animator animation) {
interaction.state.write(MotionState.AT_REST);
}
});
}
@Override
protected void onConnect(final MotionObserver<T> observer) {
updateListeners.put(observer, new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//noinspection unchecked
observer.next((T) animation.getAnimatedValue());
}
});
}
@Override
protected void onEnable() {
initialized = false;
evaluatorSubscription = interaction.evaluator.subscribe(
new SimpleMotionObserver<TypeEvaluator<T>>() {
@Override
public void next(TypeEvaluator<T> value) {
lastEvaluator = value;
startAnimator();
}
});
valuesSubscription = interaction.values.subscribe(new SimpleMotionObserver<T[]>() {
@Override
public void next(T[] value) {
lastValues = value;
startAnimator();
}
});
offsetsSubscription = interaction.offsets.subscribe(new SimpleMotionObserver<float[]>() {
@Override
public void next(float[] value) {
lastOffsets = value;
startAnimator();
}
});
timingFunctionsSubscription = interaction.timingFunctions.subscribe(
new SimpleMotionObserver<TimeInterpolator[]>() {
@Override
public void next(TimeInterpolator[] value) {
lastTimingFunctions = value;
startAnimator();
}
});
durationSubscription = interaction.duration.subscribe(new SimpleMotionObserver<Long>() {
@Override
public void next(Long value) {
lastDuration = value;
startAnimator();
}
});
delaySubscription = interaction.delay.subscribe(new SimpleMotionObserver<Long>() {
@Override
public void next(Long value) {
lastDelay = value;
startAnimator();
}
});
repeatCountSubscription = interaction.repeatCount.subscribe(new SimpleMotionObserver<Integer>() {
@Override
public void next(Integer value) {
lastRepeatCount = value;
startAnimator();
}
});
repeatModeSubscription = interaction.repeatMode.subscribe(new SimpleMotionObserver<Integer>() {
@Override
public void next(Integer value) {
lastRepeatMode = value;
startAnimator();
}
});
timingFunctionSubscription =
interaction.timingFunction.subscribe(new SimpleMotionObserver<TimeInterpolator>() {
@Override
public void next(TimeInterpolator value) {
lastTimingFunction = value;
startAnimator();
}
});
initialized = true;
startAnimator();
}
private void startAnimator() {
if (!initialized) {
return;
}
animator.cancel();
if (lastValues == null || lastValues.length == 0) {
return;
}
Object[] values;
if (lastValues.length == 1) {
values = lengthTwoArray;
values[0] = interaction.property.get(interaction.target);
values[1] = lastValues[0];
} else {
values = lastValues;
}
if (lastOffsets != null && lastOffsets.length != values.length) {
return;
}
if (lastTimingFunctions != null && lastTimingFunctions.length != values.length - 1) {
return;
}
if (lastOffsets == null && lastTimingFunctions == null) {
animator.setObjectValues(values);
} else {
Keyframe[] keyframes = new Keyframe[values.length];
for (int i = 0; i < values.length; i++) {
float offset;
if (lastOffsets != null) {
offset = lastOffsets[i];
} else {
offset = (float) i / (values.length - 1);
}
keyframes[i] = Keyframe.ofObject(offset, values[i]);
if (lastTimingFunctions != null && i >= 1) {
keyframes[i].setInterpolator(lastTimingFunctions[i - 1]);
}
}
animator.setValues(PropertyValuesHolder.ofKeyframe("", keyframes));
}
animator.setEvaluator(lastEvaluator);
if (lastDuration != null) {
animator.setDuration(lastDuration);
} else {
animator.setDuration(0);
}
if (lastDelay != null) {
animator.setStartDelay(lastDelay);
} else {
animator.setStartDelay(0);
}
if (lastRepeatCount != null) {
animator.setRepeatCount(lastRepeatCount);
} else {
animator.setRepeatCount(0);
}
if (lastRepeatMode != null) {
animator.setRepeatMode(lastRepeatMode);
} else {
animator.setRepeatMode(ValueAnimator.RESTART);
}
if (lastTimingFunction != null) {
animator.setInterpolator(lastTimingFunction);
} else {
animator.setInterpolator(defaultInterpolator);
}
animator.start();
}
@Override
protected void onDisable() {
animator.cancel();
if (evaluatorSubscription != null) {
evaluatorSubscription.unsubscribe();
valuesSubscription.unsubscribe();
offsetsSubscription.unsubscribe();
timingFunctionsSubscription.unsubscribe();
durationSubscription.unsubscribe();
delaySubscription.unsubscribe();
timingFunctionSubscription.unsubscribe();
}
}
@Override
protected void onDisconnect(MotionObserver<T> observer) {
updateListeners.remove(observer);
}
}