/* * Copyright 2016-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; import android.util.Property; import android.view.View; import com.google.android.indefinite.observable.IndefiniteObservable.Subscription; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import static com.google.android.material.motion.operators.Dedupe.dedupe; import static com.google.android.material.motion.operators.Rewrite.rewrite; /** * A MotionRuntime writes the output of streams to properties and observes their overall state. */ public final class MotionRuntime { private final List<Subscription> subscriptions = new ArrayList<>(); private final WeakHashMap<View, ReactiveView> cachedReactiveViews = new WeakHashMap<>(); private final WeakHashMap<Object, List<Interaction<?, ?>>> cachedInteractions = new WeakHashMap<>(); /** * Subscribes to the stream, writes its output to the given property, and observes its state. */ public <O, T> void write( MotionObservable<T> stream, final O target, final Property<O, T> property) { write(stream, ReactiveProperty.of(target, property)); } /** * Subscribes to the stream, writes its output to the given property, and observes its state. */ public <T> void write(MotionObservable<T> stream, final ReactiveProperty<T> property) { subscriptions.add(stream.subscribe(new MotionObserver<T>() { @Override public void next(T value) { property.write(value); } @Override public void build(MotionBuilder<T> builder, T[] values) { builder.start(property, values); } })); } @SafeVarargs public final <O, T> void addInteraction( Interaction<O, T> interaction, O target, Operation<T, T>... constraints) { addInteraction(interaction, target, new ConstraintApplicator<>(constraints)); } public final <O, T> void addInteraction( Interaction<O, T> interaction, O target, ConstraintApplicator<T> constraints) { List<Interaction<?, ?>> interactions = cachedInteractions.get(target); if (interactions == null) { interactions = new ArrayList<>(); cachedInteractions.put(target, interactions); } interactions.add(interaction); interaction.apply(this, target, constraints); } /** * Returns a reactive version of the given object and caches the returned result for future access. */ public ReactiveView get(View view) { ReactiveView reactiveView = cachedReactiveViews.get(view); if (reactiveView == null) { reactiveView = new ReactiveView(view); cachedReactiveViews.put(view, reactiveView); } return reactiveView; } public <I extends Interaction<O, ?>, O> List<I> interactions(Class<I> klass, O target) { List<Interaction<?, ?>> interactions = cachedInteractions.get(target); List<I> filteredInteractions = new ArrayList<>(); for (Interaction<?, ?> i : interactions) { if(klass.isInstance(i)) //noinspection unchecked filteredInteractions.add((I)i); } return filteredInteractions; } /** * Initiates interaction {@code a} when interaction {@code b} changes to the given state. */ public void start(Interaction<?, ?> a, Interaction<?, ?> b, @MotionState int state) { MotionObservable<Boolean> stream = b.state.getStream() .compose(dedupe()) .compose(rewrite(state, true)); write(stream, a.enabled); } }