/* * Copyright (c) 2017-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.litho.animation; import java.util.HashSet; import java.util.concurrent.CopyOnWriteArrayList; import android.support.v4.util.SimpleArrayMap; import com.facebook.litho.dataflow.ChoreographerCompat; import com.facebook.litho.internal.ArraySet; /** * An {@link AnimationBinding} that's composed of other {@link AnimationBinding}s running in * parallel, possibly starting on a stagger. */ public class ParallelBinding implements AnimationBinding { private final CopyOnWriteArrayList<AnimationBindingListener> mListeners = new CopyOnWriteArrayList<>(); private final AnimationBinding[] mBindings; private final AnimationBindingListener mChildListener; private final HashSet<AnimationBinding> mBindingsFinished = new HashSet<>(); private final ChoreographerCompat.FrameCallback mStaggerCallback; private final int mStaggerMs; private int mNextIndexToStart = 0; private int mChildrenFinished = 0; private boolean mHasStarted = false; private boolean mIsActive = false; private Resolver mResolver; public ParallelBinding(int staggerMs, AnimationBinding... bindings) { mStaggerMs = staggerMs; mBindings = bindings; if (mBindings.length == 0) { throw new IllegalArgumentException("Empty binding parallel"); } mChildListener = new AnimationBindingListener() { @Override public void onStart(AnimationBinding binding) { } @Override public void onFinish(AnimationBinding binding) { ParallelBinding.this.onBindingFinished(binding); } }; if (mStaggerMs == 0) { mStaggerCallback = null; } else { mStaggerCallback = new ChoreographerCompat.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { if (!mIsActive) { return; } startNextBindingForStagger(); } }; } } private void onBindingFinished(AnimationBinding binding) { if (mBindingsFinished.contains(binding)) { throw new RuntimeException("Binding unexpectedly completed twice"); } mBindingsFinished.add(binding); mChildrenFinished++; binding.removeListener(mChildListener); if (mChildrenFinished >= mBindings.length) { finish(); } } private void finish() { mIsActive = false; for (AnimationBindingListener listener : mListeners) { listener.onFinish(this); } } @Override public void start(Resolver resolver) { if (mHasStarted) { throw new RuntimeException("Starting binding multiple times"); } mHasStarted = true; mIsActive = true; mResolver = resolver; for (AnimationBindingListener listener : mListeners) { listener.onStart(this); } for (AnimationBinding binding : mBindings) { binding.addListener(mChildListener); } if (mStaggerMs == 0) { for (int i = 0; i < mBindings.length; i++) { final AnimationBinding binding = mBindings[i]; binding.start(mResolver); } mNextIndexToStart = mBindings.length; } else { startNextBindingForStagger(); } } private void startNextBindingForStagger() { mBindings[mNextIndexToStart].start(mResolver); mNextIndexToStart++; if (mNextIndexToStart < mBindings.length) { ChoreographerCompat.getInstance().postFrameCallbackDelayed(mStaggerCallback, mStaggerMs); } } @Override public void stop() { if (!mIsActive) { return; } mIsActive = false; mResolver = null; for (int i = 0; i < mBindings.length; i++) { final AnimationBinding childBinding = mBindings[i]; if (childBinding.isActive()) { childBinding.stop(); } } } @Override public boolean isActive() { return mIsActive; } @Override public void collectTransitioningProperties(ArraySet<ComponentProperty> outSet) { for (int i = 0; i < mBindings.length; i++) { mBindings[i].collectTransitioningProperties(outSet); } } @Override public void collectAppearFromValues(SimpleArrayMap<ComponentProperty, RuntimeValue> outMap) { for (int i = 0; i < mBindings.length; i++) { mBindings[i].collectAppearFromValues(outMap); } } @Override public void collectDisappearToValues(SimpleArrayMap<ComponentProperty, RuntimeValue> outMap) { for (int i = 0; i < mBindings.length; i++) { mBindings[i].collectDisappearToValues(outMap); } } @Override public void addListener(AnimationBindingListener animationBindingListener) { mListeners.add(animationBindingListener); } @Override public void removeListener(AnimationBindingListener animationBindingListener) { mListeners.remove(animationBindingListener); } }