diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 7bfb76a978..34a6821ac5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -21,7 +21,8 @@ import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRES import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_PROGRESS; +import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA; +import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION; import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; @@ -40,11 +41,9 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.TouchController; import com.android.quickstep.TaskUtils; @@ -148,16 +147,10 @@ public class NavBarToHomeTouchController implements TouchController, AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU); } else if (mStartState == ALL_APPS) { AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); - builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_PROGRESS, - -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR); - - // Slightly fade out all apps content to further distinguish from scrolling. - StateAnimationConfig config = new StateAnimationConfig(); - config.duration = accuracy; - config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators - .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f)); - - allAppsController.setAlphas(mEndState, config, builder); + builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_TRANSLATION, + -mPullbackDistance, PULLBACK_INTERPOLATOR); + builder.setFloat(allAppsController, ALL_APPS_PULL_BACK_ALPHA, + 0.5f, PULLBACK_INTERPOLATOR); } AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 8662d0067d..637a4189b8 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -44,6 +44,8 @@ import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.util.MultiAdditivePropertyFactory; +import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.views.ScrimView; @@ -76,20 +78,56 @@ public class AllAppsTransitionController } }; - public static final FloatProperty ALL_APPS_PULL_BACK_PROGRESS = - new FloatProperty("allAppsPullBackProgress") { + public static final FloatProperty ALL_APPS_PULL_BACK_TRANSLATION = + new FloatProperty("allAppsPullBackTranslation") { @Override public Float get(AllAppsTransitionController controller) { - return controller.mPullBackProgress; + if (controller.mIsTablet) { + return controller.mAppsView.getRecyclerViewContainer().getTranslationY(); + } else { + return controller.getAppsViewPullbackTranslationY().get( + controller.mAppsView); + } } @Override - public void setValue(AllAppsTransitionController controller, float progress) { - controller.setPullBackProgress(progress); + public void setValue(AllAppsTransitionController controller, float translation) { + if (controller.mIsTablet) { + controller.mAppsView.getRecyclerViewContainer().setTranslationY( + translation); + } else { + controller.getAppsViewPullbackTranslationY().set(controller.mAppsView, + translation); + } } }; + public static final FloatProperty ALL_APPS_PULL_BACK_ALPHA = + new FloatProperty("allAppsPullBackAlpha") { + + @Override + public Float get(AllAppsTransitionController controller) { + if (controller.mIsTablet) { + return controller.mAppsView.getRecyclerViewContainer().getAlpha(); + } else { + return controller.getAppsViewPullbackAlpha().getValue(); + } + } + + @Override + public void setValue(AllAppsTransitionController controller, float alpha) { + if (controller.mIsTablet) { + controller.mAppsView.getRecyclerViewContainer().setAlpha(alpha); + } else { + controller.getAppsViewPullbackAlpha().setValue(alpha); + } + } + }; + + private static final int INDEX_APPS_VIEW_PROGRESS = 0; + private static final int INDEX_APPS_VIEW_PULLBACK = 1; + private static final int APPS_VIEW_INDEX_COUNT = 2; private ActivityAllAppsContainerView mAppsView; @@ -104,18 +142,23 @@ public class AllAppsTransitionController // When {@link mProgress} is 1, all apps container is pulled down. private float mShiftRange; // changes depending on the orientation private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent - private float mPullBackProgress; // [0, 1], mShiftRange * mPullBackProgress = shiftCurrent private ScrimView mScrimView; - private View mPullBackView; + + private final MultiAdditivePropertyFactory + mAppsViewTranslationYPropertyFactory = new MultiAdditivePropertyFactory<>( + "appsViewTranslationY", View.TRANSLATION_Y); + private MultiValueAlpha mAppsViewAlpha; + + private boolean mIsTablet; public AllAppsTransitionController(Launcher l) { mLauncher = l; DeviceProfile dp = mLauncher.getDeviceProfile(); setShiftRange(dp.allAppsShiftRange); mProgress = 1f; - mPullBackProgress = 1f; mIsVerticalLayout = dp.isVerticalBarLayout(); + mIsTablet = dp.isTablet; mLauncher.addOnDeviceProfileChangeListener(this); } @@ -133,7 +176,7 @@ public class AllAppsTransitionController mLauncher.getWorkspace().getPageIndicator().setTranslationY(0); } - mPullBackView = dp.isTablet ? mAppsView.getRecyclerViewContainer() : mAppsView; + mIsTablet = dp.isTablet; } /** @@ -146,16 +189,27 @@ public class AllAppsTransitionController */ public void setProgress(float progress) { mProgress = progress; - mAppsView.setTranslationY(mProgress * mShiftRange); + getAppsViewProgressTranslationY().set(mAppsView, mProgress * mShiftRange); } public float getProgress() { return mProgress; } - private void setPullBackProgress(float progress) { - mPullBackProgress = progress; - mPullBackView.setTranslationY(mPullBackProgress * mShiftRange); + private FloatProperty getAppsViewProgressTranslationY() { + return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PROGRESS); + } + + private FloatProperty getAppsViewPullbackTranslationY() { + return mAppsViewTranslationYPropertyFactory.get(INDEX_APPS_VIEW_PULLBACK); + } + + private MultiValueAlpha.AlphaProperty getAppsViewProgressAlpha() { + return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PROGRESS); + } + + private MultiValueAlpha.AlphaProperty getAppsViewPullbackAlpha() { + return mAppsViewAlpha.getProperty(INDEX_APPS_VIEW_PULLBACK); } /** @@ -164,8 +218,6 @@ public class AllAppsTransitionController */ @Override public void setState(LauncherState state) { - // Always reset pull back progress when switching states. - setPullBackProgress(0f); setProgress(state.getVerticalProgress(mLauncher)); setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER); onProgressAnimationEnd(); @@ -180,10 +232,13 @@ public class AllAppsTransitionController StateAnimationConfig config, PendingAnimation builder) { if (NORMAL.equals(toState) && mLauncher.isInState(ALL_APPS)) { UiThreadHelper.hideKeyboardAsync(mLauncher, mLauncher.getAppsView().getWindowToken()); + builder.addEndListener(success -> { + // Reset pull back progress and alpha after switching states. + ALL_APPS_PULL_BACK_TRANSLATION.set(this, 0f); + ALL_APPS_PULL_BACK_ALPHA.set(this, 1f); + }); } - // Always reset pull back progress when switching states. - setPullBackProgress(0f); float targetProgress = toState.getVerticalProgress(mLauncher); if (Float.compare(mProgress, targetProgress) == 0) { setAlphas(toState, config, builder); @@ -222,7 +277,8 @@ public class AllAppsTransitionController boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0; Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR); - setter.setViewAlpha(mAppsView, hasAllAppsContent ? 1 : 0, allAppsFade); + setter.setFloat(getAppsViewProgressAlpha(), MultiValueAlpha.VALUE, + hasAllAppsContent ? 1 : 0, allAppsFade); boolean shouldProtectHeader = ALL_APPS == state || mLauncher.getStateManager().getState() == ALL_APPS; @@ -245,8 +301,8 @@ public class AllAppsTransitionController | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } mAppsView.setScrimView(scrimView); - mPullBackView = mLauncher.getDeviceProfile().isTablet - ? mAppsView.getRecyclerViewContainer() : mAppsView; + mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT); + mAppsViewAlpha.setUpdateVisibility(true); } /** diff --git a/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java new file mode 100644 index 0000000000..50f7027407 --- /dev/null +++ b/src/com/android/launcher3/util/MultiAdditivePropertyFactory.java @@ -0,0 +1,115 @@ +/* + * 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.util.ArrayMap; +import android.util.FloatProperty; +import android.util.Log; +import android.util.Property; +import android.view.View; + +/** + * 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 sets the addition of all values. + * + * @param Type where to apply the property. + */ +public class MultiAdditivePropertyFactory { + + private static final boolean DEBUG = false; + private static final String TAG = "MultiAdditivePropertyFactory"; + private final String mName; + private final ArrayMap mProperties = + new ArrayMap<>(); + + // This is an optimization for cases when set is called repeatedly with the same setterIndex. + private float mAggregationOfOthers = 0f; + private Integer mLastIndexSet = -1; + private final Property mProperty; + + public MultiAdditivePropertyFactory(String name, Property property) { + mName = name; + mProperty = property; + } + + /** Returns the [MultiFloatProperty] associated with [inx], creating it if not present. */ + public MultiAdditiveProperty get(Integer index) { + return mProperties.computeIfAbsent(index, + (k) -> new MultiAdditiveProperty(index, mName + "_" + index)); + } + + /** + * Each [setValue] will be aggregated with the other properties values created by the + * corresponding factory. + */ + class MultiAdditiveProperty extends FloatProperty { + private final int mInx; + private float mValue = 0f; + + MultiAdditiveProperty(int inx, String name) { + super(name); + mInx = inx; + } + + @Override + public void setValue(T obj, float newValue) { + if (mLastIndexSet != mInx) { + mAggregationOfOthers = 0f; + mProperties.forEach((key, property) -> { + if (key != mInx) { + mAggregationOfOthers += property.mValue; + } + }); + mLastIndexSet = mInx; + } + float lastAggregatedValue = mAggregationOfOthers + newValue; + mValue = newValue; + apply(obj, lastAggregatedValue); + + if (DEBUG) { + Log.d(TAG, "name=" + mName + + " newValue=" + newValue + " mInx=" + mInx + + " aggregated=" + lastAggregatedValue + " others= " + mProperties); + } + } + + @Override + public Float get(T view) { + // The scale of the view should match mLastAggregatedValue. Still, if it has been + // changed without using this property, it can differ. As this get method is usually + // used to set the starting point on an animation, this would result in some jumps + // when the view scale is different than the last aggregated value. To stay on the + // safe side, let's return the real view scale. + return mProperty.get(view); + } + + @Override + public String toString() { + return String.valueOf(mValue); + } + } + + protected void apply(View view, float value) { + mProperty.set(view, value); + } +} diff --git a/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt new file mode 100644 index 0000000000..309d055bad --- /dev/null +++ b/tests/src/com/android/launcher3/util/MultiAdditivePropertyTest.kt @@ -0,0 +1,66 @@ +/* + * 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.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +/** Unit tests for [MultiAdditivePropertyFactory] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class MultiAdditivePropertyTest { + + private val received = mutableListOf() + + private val factory = + object : MultiAdditivePropertyFactory("Test", View.TRANSLATION_X) { + override fun apply(obj: View?, value: Float) { + received.add(value) + } + } + + private val p1 = factory.get(1) + private val p2 = factory.get(2) + private val p3 = factory.get(3) + + @Test + fun set_sameIndexes_allApplied() { + val v1 = 50f + val v2 = 100f + p1.set(null, v1) + p1.set(null, v1) + p1.set(null, v2) + + assertThat(received).containsExactly(v1, v1, v2) + } + + @Test + fun set_differentIndexes_aggregationApplied() { + val v1 = 50f + val v2 = 100f + val v3 = 150f + p1.set(null, v1) + p2.set(null, v2) + p3.set(null, v3) + + assertThat(received).containsExactly(v1, v1 + v2, v1 + v2 + v3) + } +} diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt index 6099987172..7d92214d9b 100644 --- a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt +++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt @@ -1,3 +1,19 @@ +/* + * 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.view.View