Implementing an unfold animation using CannedAnimationController
Bug: 297057373
Flag: aconfig enable_unfold_state_animation DISABLED
Test: Manual
Change-Id: Iaa352133ebf06ab74b9ba4b8c4d3523f27666089
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index f34105a..5703711 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -127,3 +127,10 @@
description: "Enables asnc inflation of workspace icons"
bug: "318539160"
}
+
+flag {
+ name: "enable_unfold_state_animation"
+ namespace: "launcher"
+ description: "Tie unfold animation with state animation"
+ bug: "297057373"
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 33641a4..7c7c426 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
@@ -405,8 +406,12 @@
*/
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
StatefulActivity activity) {
- if (activity instanceof QuickstepLauncher) {
- return ((QuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
+ if (!enableUnfoldStateAnimation()) {
+ if (activity instanceof QuickstepLauncher ql) {
+ return ql.getUnfoldTransitionProgressProvider();
+ }
+ } else {
+ return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
}
return null;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f0ab08c..b1ea0de 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
@@ -170,6 +171,8 @@
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
+import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -955,9 +958,17 @@
}
private void initUnfoldTransitionProgressProvider() {
- final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
- if (config.isEnabled()) {
- initRemotelyCalculatedUnfoldAnimation(config);
+ if (!enableUnfoldStateAnimation()) {
+ final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
+ if (config.isEnabled()) {
+ initRemotelyCalculatedUnfoldAnimation(config);
+ }
+ } else {
+ ProxyUnfoldTransitionProvider provider =
+ SystemUiProxy.INSTANCE.get(this).getUnfoldTransitionProvider();
+ if (provider != null) {
+ new LauncherUnfoldTransitionController(this, provider);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 723af43..56a4024 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,6 +17,7 @@
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -67,6 +68,7 @@
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistUtils;
+import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -74,6 +76,7 @@
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
@@ -177,7 +180,10 @@
*/
private final PendingIntent mRecentsPendingIntent;
- public SystemUiProxy(Context context) {
+ @Nullable
+ private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;
+
+ private SystemUiProxy(Context context) {
mContext = context;
mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
@@ -187,6 +193,10 @@
mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
| Intent.FILL_IN_COMPONENT, options.toBundle());
+
+ mUnfoldTransitionProvider =
+ (enableUnfoldStateAnimation() && new ResourceUnfoldTransitionConfig().isEnabled())
+ ? new ProxyUnfoldTransitionProvider() : null;
}
@Override
@@ -251,7 +261,7 @@
mRecentTasks = recentTasks;
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
- mUnfoldAnimation = unfoldAnimation;
+ mUnfoldAnimation = enableUnfoldStateAnimation() ? null : unfoldAnimation;
mDragAndDrop = dragAndDrop;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
@@ -272,6 +282,19 @@
setAssistantOverridesRequested(
AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
mStateChangeCallbacks.forEach(Runnable::run);
+
+ if (mUnfoldTransitionProvider != null) {
+ if (unfoldAnimation != null) {
+ try {
+ unfoldAnimation.setListener(mUnfoldTransitionProvider);
+ mUnfoldTransitionProvider.setActive(true);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ } else {
+ mUnfoldTransitionProvider.setActive(false);
+ }
+ }
}
/**
@@ -1451,6 +1474,11 @@
}
}
+ @Nullable
+ public ProxyUnfoldTransitionProvider getUnfoldTransitionProvider() {
+ return mUnfoldTransitionProvider;
+ }
+
//
// Recents
//
diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt
new file mode 100644
index 0000000..54d317d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherUnfoldTransitionController.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 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.quickstep.util.unfold
+
+import android.app.Activity
+import android.os.Trace
+import android.view.Surface
+import com.android.launcher3.Alarm
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
+import com.android.launcher3.Launcher
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/** Controls animations that are happening during unfolding foldable devices */
+class LauncherUnfoldTransitionController(
+ private val launcher: Launcher,
+ private val progressProvider: ProxyUnfoldTransitionProvider
+) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener {
+
+ private var isTablet: Boolean? = null
+ private var hasUnfoldTransitionStarted = false
+ private val timeoutAlarm =
+ Alarm().apply {
+ setOnAlarmListener {
+ onTransitionFinished()
+ Trace.endAsyncSection("$TAG#startedPreemptively", 0)
+ }
+ }
+
+ init {
+ launcher.addOnDeviceProfileChangeListener(this)
+ launcher.registerActivityLifecycleCallbacks(this)
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ progressProvider.removeCallback(this)
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ progressProvider.addCallback(this)
+ }
+
+ override fun onDeviceProfileChanged(dp: DeviceProfile) {
+ if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
+ return
+ }
+
+ if (isTablet != null && dp.isTablet != isTablet) {
+ // We should preemptively start the animation only if:
+ // - We changed to the unfolded screen
+ // - SystemUI IPC connection is alive, so we won't end up in a situation that we won't
+ // receive transition progress events from SystemUI later because there was no
+ // IPC connection established (e.g. because of SystemUI crash)
+ // - SystemUI has not already sent unfold animation progress events. This might happen
+ // if Launcher was not open during unfold, in this case we receive the configuration
+ // change only after we went back to home screen and we don't want to start the
+ // animation in this case.
+ if (dp.isTablet && progressProvider.isActive && !hasUnfoldTransitionStarted) {
+ // Preemptively start the unfold animation to make sure that we have drawn
+ // the first frame of the animation before the screen gets unblocked
+ onTransitionStarted()
+ Trace.beginAsyncSection("$TAG#startedPreemptively", 0)
+ timeoutAlarm.setAlarm(PREEMPTIVE_UNFOLD_TIMEOUT_MS)
+ }
+ if (!dp.isTablet) {
+ // Reset unfold transition status when folded
+ hasUnfoldTransitionStarted = false
+ }
+ }
+
+ isTablet = dp.isTablet
+ }
+
+ override fun onTransitionStarted() {
+ hasUnfoldTransitionStarted = true
+ launcher.animationCoordinator.setAnimation(
+ provider = this,
+ factory = this::onPrepareUnfoldAnimation,
+ duration =
+ 1000L // The expected duration for the animation. Then only comes to play if we have
+ // to run the animation ourselves in case sysui misses the end signal
+ )
+ timeoutAlarm.cancelAlarm()
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ hasUnfoldTransitionStarted = true
+ launcher.animationCoordinator.getPlaybackController(this)?.setPlayFraction(progress)
+ }
+
+ override fun onTransitionFinished() {
+ // Run the animation to end the animation in case it is not already at end progress. It
+ // will scale the duration to the remaining progress
+ launcher.animationCoordinator.getPlaybackController(this)?.start()
+ timeoutAlarm.cancelAlarm()
+ }
+
+ private fun onPrepareUnfoldAnimation(anim: PendingAnimation) {
+ val dp = launcher.deviceProfile
+ val rotation = dp.displayInfo.rotation
+ val isVertical = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+ UnfoldAnimationBuilder.buildUnfoldAnimation(
+ launcher,
+ isVertical,
+ dp.displayInfo.currentSize,
+ anim
+ )
+ }
+
+ companion object {
+ private const val TAG = "LauncherUnfoldTransitionController"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/unfold/ProxyUnfoldTransitionProvider.kt b/quickstep/src/com/android/quickstep/util/unfold/ProxyUnfoldTransitionProvider.kt
new file mode 100644
index 0000000..83c7f72
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/unfold/ProxyUnfoldTransitionProvider.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.quickstep.util.unfold
+
+import androidx.annotation.AnyThread
+import androidx.annotation.FloatRange
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.progress.IUnfoldTransitionListener
+import com.android.systemui.unfold.progress.UnfoldRemoteFilter
+
+/** Receives unfold events from remote senders (System UI). */
+class ProxyUnfoldTransitionProvider :
+ UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
+
+ private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
+ private val delegate = UnfoldRemoteFilter(ProcessedProgressListener())
+
+ private var transitionStarted = false
+ var isActive = false
+ set(value) {
+ field = value
+ if (!value) {
+ // Finish any active transition
+ onTransitionFinished()
+ }
+ }
+
+ @AnyThread
+ override fun onTransitionStarted() {
+ MAIN_EXECUTOR.execute(delegate::onTransitionStarted)
+ }
+
+ @AnyThread
+ override fun onTransitionProgress(progress: Float) {
+ MAIN_EXECUTOR.execute { delegate.onTransitionProgress(progress) }
+ }
+
+ @AnyThread
+ override fun onTransitionFinished() {
+ MAIN_EXECUTOR.execute(delegate::onTransitionFinished)
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners += listener
+ if (transitionStarted) {
+ // Update the listener in case there was is an active transition
+ listener.onTransitionStarted()
+ }
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners -= listener
+ if (transitionStarted) {
+ // Finish the transition if it was already running
+ listener.onTransitionFinished()
+ }
+ }
+
+ override fun destroy() {
+ listeners.clear()
+ }
+
+ private inner class ProcessedProgressListener : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ if (!transitionStarted) {
+ transitionStarted = true
+ listeners.forEach(TransitionProgressListener::onTransitionStarted)
+ }
+ }
+
+ override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+
+ override fun onTransitionFinished() {
+ if (transitionStarted) {
+ transitionStarted = false
+ listeners.forEach(TransitionProgressListener::onTransitionFinished)
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
new file mode 100644
index 0000000..d2c4728
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.quickstep.util.unfold
+
+import android.graphics.Point
+import android.view.ViewGroup
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.clampToProgress
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y
+import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.Workspace
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.util.HorizontalInsettableView
+
+private typealias ViewGroupAction = (ViewGroup, Boolean) -> Unit
+
+object UnfoldAnimationBuilder {
+
+ private val CLIP_CHILDREN: ViewGroupAction = ViewGroup::setClipChildren
+ private val CLIP_TO_PADDING: ViewGroupAction = ViewGroup::setClipToPadding
+
+ data class RestoreInfo(val action: ViewGroupAction, var target: ViewGroup, var value: Boolean)
+
+ // Percentage of the width of the quick search bar that will be reduced
+ // from the both sides of the bar when progress is 0
+ private const val MAX_WIDTH_INSET_FRACTION = 0.04f
+
+ // Scale factor for the whole workspace and hotseat
+ private const val SCALE_LAUNCHER_FROM = 0.92f
+
+ // Translation factor for all the items on the homescreen
+ private const val TRANSLATION_PERCENTAGE = 0.08f
+
+ private fun setClipChildren(
+ target: ViewGroup,
+ value: Boolean,
+ restoreList: MutableList<RestoreInfo>
+ ) {
+ val originalValue = target.clipChildren
+ if (originalValue != value) {
+ target.clipChildren = value
+ restoreList.add(RestoreInfo(CLIP_CHILDREN, target, originalValue))
+ }
+ }
+
+ private fun setClipToPadding(
+ target: ViewGroup,
+ value: Boolean,
+ restoreList: MutableList<RestoreInfo>
+ ) {
+ val originalValue = target.clipToPadding
+ if (originalValue != value) {
+ target.clipToPadding = value
+ restoreList.add(RestoreInfo(CLIP_TO_PADDING, target, originalValue))
+ }
+ }
+
+ private fun addChildrenAnimation(
+ itemsContainer: ViewGroup,
+ isVerticalFold: Boolean,
+ screenSize: Point,
+ anim: PendingAnimation
+ ) {
+ val tempLocation = IntArray(2)
+ for (i in 0 until itemsContainer.childCount) {
+ val child = itemsContainer.getChildAt(i)
+
+ child.getLocationOnScreen(tempLocation)
+ if (isVerticalFold) {
+ val viewCenterX = tempLocation[0] + child.width / 2
+ val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
+ anim.addFloat(
+ child,
+ VIEW_TRANSLATE_X,
+ distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
+ 0f,
+ LINEAR
+ )
+ } else {
+ val viewCenterY = tempLocation[1] + child.height / 2
+ val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
+ anim.addFloat(
+ child,
+ VIEW_TRANSLATE_Y,
+ distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
+ 0f,
+ LINEAR
+ )
+ }
+ }
+ }
+
+ /**
+ * Builds an animation for the unfold experience and adds it to the provided PendingAnimation
+ */
+ fun buildUnfoldAnimation(
+ launcher: Launcher,
+ isVerticalFold: Boolean,
+ screenSize: Point,
+ anim: PendingAnimation
+ ) {
+ val restoreList = ArrayList<RestoreInfo>()
+ val registerViews: (CellLayout) -> Unit = { cellLayout ->
+ setClipChildren(cellLayout, false, restoreList)
+ setClipToPadding(cellLayout, false, restoreList)
+ addChildrenAnimation(cellLayout.shortcutsAndWidgets, isVerticalFold, screenSize, anim)
+ }
+
+ val workspace: Workspace<*> = launcher.workspace
+ val hotseat = launcher.hotseat
+
+ // Animation icons from workspace for all orientations
+ workspace.forEachVisiblePage { registerViews(it as CellLayout) }
+ setClipChildren(workspace, false, restoreList)
+ setClipToPadding(workspace, true, restoreList)
+
+ // Workspace scale
+ launcher.workspace.setPivotToScaleWithSelf(launcher.hotseat)
+ val interpolator = clampToProgress(LINEAR, 0f, 1f)
+ anim.addFloat(
+ workspace,
+ WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
+ SCALE_LAUNCHER_FROM,
+ 1f,
+ interpolator
+ )
+ anim.addFloat(
+ hotseat,
+ HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
+ SCALE_LAUNCHER_FROM,
+ 1f,
+ interpolator
+ )
+
+ if (isVerticalFold) {
+ if (hotseat.qsb is HorizontalInsettableView) {
+ anim.addFloat(
+ hotseat.qsb as HorizontalInsettableView,
+ HorizontalInsettableView.HORIZONTAL_INSETS,
+ MAX_WIDTH_INSET_FRACTION,
+ 0f,
+ LINEAR
+ )
+ }
+ registerViews(hotseat)
+ }
+ anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
+ }
+}