/* * Copyright (C) 2022 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 com.android.launcher3.util; import android.animation.Animator; import android.animation.ObjectAnimator; import android.util.FloatProperty; import android.util.Log; import java.io.PrintWriter; import java.util.Arrays; /** * Allows to combine multiple values set by several sources. * * The various sources are meant to use [set], providing different `setterIndex` params. When it is * not set, 0 is used. This is meant to cover the case multiple animations are going on at the same * time. * * This class behaves similarly to [MultiValueAlpha], but is meant to be more abstract and reusable. * It aggregate all values using the provided [aggregator]. * * @param Type where to apply the property. */ public class MultiPropertyFactory { public static final FloatProperty.MultiProperty> MULTI_PROPERTY_VALUE = new FloatProperty.MultiProperty>("value") { @Override public Float get(MultiPropertyFactory.MultiProperty property) { return property.mValue; } @Override public void setValue(MultiPropertyFactory.MultiProperty property, float value) { property.setValue(value); } }; private static final boolean DEBUG = false; private static final String TAG = "MultiPropertyFactory"; private final MultiPropertyFactory.MultiProperty[] mProperties; // This is an optimization for cases when set is called repeatedly with the same setterIndex. private float mAggregationOfOthers = 0f; private int mLastIndexSet = -1; protected final T mTarget; private final FloatProperty mProperty; private final FloatBiFunction mAggregator; /** * Represents a function that accepts two float and produces a float. */ public interface FloatBiFunction { /** * Applies this function to the given arguments. */ float apply(float a, float b); } public MultiPropertyFactory(T target, FloatProperty property, int size, FloatBiFunction aggregator) { this(target, property, size, aggregator, 0); } public MultiPropertyFactory(T target, FloatProperty property, int size, FloatBiFunction aggregator, float defaultPropertyValue) { mTarget = target; mProperty = property; mAggregator = aggregator; mProperties = new MultiPropertyFactory.MultiProperty[size]; for (int i = 0; i < size; i++) { mProperties[i] = new MultiProperty(i, defaultPropertyValue); } } /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */ public MultiProperty get(int index) { return (MultiProperty) mProperties[index]; } @Override public String toString() { return Arrays.deepToString(mProperties); } /** * Dumps the alpha channel values to the given PrintWriter * * @param prefix String to be used before every line * @param pw PrintWriter where the logs should be dumped * @param label String used to help identify this object * @param alphaIndexLabels Strings that represent each alpha channel, these should be entered * in the order of the indexes they represent, starting from 0. */ public void dump(String prefix, PrintWriter pw, String label, String... alphaIndexLabels) { pw.println(prefix + label); String innerPrefix = prefix + '\t'; for (int i = 0; i < alphaIndexLabels.length; i++) { if (i >= mProperties.length) { pw.println(innerPrefix + alphaIndexLabels[i] + " given for alpha index " + i + " however there are only " + mProperties.length + " alpha channels."); continue; } pw.println(innerPrefix + alphaIndexLabels[i] + "=" + get(i).getValue()); } } /** * Each [setValue] will be aggregated with the other properties values created by the * corresponding factory. */ public class MultiProperty { private final int mInx; private final float mDefaultValue; private float mValue; MultiProperty(int inx, float defaultValue) { mInx = inx; mDefaultValue = defaultValue; mValue = defaultValue; } public void setValue(float newValue) { if (mLastIndexSet != mInx) { mAggregationOfOthers = mDefaultValue; for (MultiPropertyFactory.MultiProperty other : mProperties) { if (other.mInx != mInx) { mAggregationOfOthers = mAggregator.apply(mAggregationOfOthers, other.mValue); } } mLastIndexSet = mInx; } float lastAggregatedValue = mAggregator.apply(mAggregationOfOthers, newValue); mValue = newValue; apply(lastAggregatedValue); if (DEBUG) { Log.d(TAG, "name=" + mProperty.getName() + " target=" + mTarget.getClass() + " newValue=" + newValue + " mInx=" + mInx + " aggregated=" + lastAggregatedValue + " others= " + Arrays.deepToString(mProperties)); } } public float getValue() { return mValue; } @Override public String toString() { return String.valueOf(mValue); } /** * Creates and returns an Animator from the current value to the given value. Future * animator on the same target automatically cancels the previous one. */ public Animator animateToValue(float value) { ObjectAnimator animator = ObjectAnimator.ofFloat(this, MULTI_PROPERTY_VALUE, value); animator.setAutoCancel(true); return animator; } } protected void apply(float value) { mProperty.set(mTarget, value); } }