Add unfold animation to launcher icons and widgets
Adds unfold animation to launcher which translates icons and widgets from the center to the sides on foldable devices. Bug: 193794541 Test: manual Change-Id: I9d6e018a0451d342f02dddea47bc180781c31d43
This commit is contained in:
parent
4b878f5318
commit
5e8dbe77e8
|
@ -36,9 +36,13 @@ import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SY
|
|||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
|
@ -75,9 +79,14 @@ import com.android.quickstep.SysUINavigationMode;
|
|||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.LauncherUnfoldAnimationController;
|
||||
import com.android.quickstep.util.ProxyScreenStatusProvider;
|
||||
import com.android.quickstep.util.QuickstepOnboardingPrefs;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.unfold.UnfoldTransitionFactory;
|
||||
import com.android.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.unfold.config.UnfoldTransitionConfig;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
@ -97,10 +106,51 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
|||
private FixedContainerItems mAllAppsPredictions;
|
||||
private HotseatPredictionController mHotseatPredictionController;
|
||||
|
||||
@Nullable
|
||||
private LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
|
||||
|
||||
@Override
|
||||
protected void setupViews() {
|
||||
super.setupViews();
|
||||
mHotseatPredictionController = new HotseatPredictionController(this);
|
||||
|
||||
final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
|
||||
if (config.isEnabled()) {
|
||||
final UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
|
||||
UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
|
||||
this,
|
||||
config,
|
||||
ProxyScreenStatusProvider.INSTANCE,
|
||||
getSystemService(DeviceStateManager.class),
|
||||
getSystemService(SensorManager.class),
|
||||
getMainThreadHandler(),
|
||||
getMainExecutor()
|
||||
);
|
||||
|
||||
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
|
||||
this,
|
||||
getWindowManager(),
|
||||
unfoldTransitionProgressProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onPause();
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -231,6 +281,10 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
|||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mHotseatPredictionController.destroy();
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -100,6 +100,7 @@ import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
|
|||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.util.AssistantUtilities;
|
||||
import com.android.quickstep.util.ProtoTracer;
|
||||
import com.android.quickstep.util.ProxyScreenStatusProvider;
|
||||
import com.android.quickstep.util.SplitScreenBounds;
|
||||
import com.android.systemui.plugins.OverscrollPlugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
|
@ -268,6 +269,12 @@ public class TouchInteractionService extends Service implements PluginListener<O
|
|||
MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onScreenTurnedOn() {
|
||||
MAIN_EXECUTOR.execute(ProxyScreenStatusProvider.INSTANCE::onScreenTurnedOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
|
||||
|
||||
/**
|
||||
* Controls animations that are happening during unfolding foldable devices
|
||||
*/
|
||||
public class LauncherUnfoldAnimationController {
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
|
||||
private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation;
|
||||
|
||||
private final AnimationListener mAnimationListener = new AnimationListener();
|
||||
|
||||
private boolean mIsTransitionRunning = false;
|
||||
private boolean mIsReadyToPlayAnimation = false;
|
||||
|
||||
public LauncherUnfoldAnimationController(
|
||||
Launcher launcher,
|
||||
WindowManager windowManager,
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
|
||||
mLauncher = launcher;
|
||||
mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
|
||||
mMoveFromCenterWorkspaceAnimation = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
|
||||
windowManager);
|
||||
mUnfoldTransitionProgressProvider.addCallback(mAnimationListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launcher is resumed
|
||||
*/
|
||||
public void onResume() {
|
||||
final ViewTreeObserver obs = mLauncher.getWorkspace().getViewTreeObserver();
|
||||
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (obs.isAlive()) {
|
||||
onPreDrawAfterResume();
|
||||
obs.removeOnPreDrawListener(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launcher activity is paused
|
||||
*/
|
||||
public void onPause() {
|
||||
mIsReadyToPlayAnimation = false;
|
||||
|
||||
if (mIsTransitionRunning) {
|
||||
mIsTransitionRunning = false;
|
||||
mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launcher activity is destroyed
|
||||
*/
|
||||
public void onDestroy() {
|
||||
mUnfoldTransitionProgressProvider.removeCallback(mAnimationListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after performing layouting of the views after configuration change
|
||||
*/
|
||||
private void onPreDrawAfterResume() {
|
||||
mIsReadyToPlayAnimation = true;
|
||||
|
||||
if (mIsTransitionRunning) {
|
||||
mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimationListener implements TransitionProgressListener {
|
||||
|
||||
@Override
|
||||
public void onTransitionStarted() {
|
||||
mIsTransitionRunning = true;
|
||||
|
||||
if (mIsReadyToPlayAnimation) {
|
||||
mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
if (mIsReadyToPlayAnimation) {
|
||||
mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
|
||||
}
|
||||
|
||||
mIsTransitionRunning = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionProgress(float progress) {
|
||||
mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.unfold.updates.screen.ScreenStatusProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Screen status provider implementation that exposes methods to provide screen
|
||||
* status updates to listeners. It is used to receive screen turned on event from
|
||||
* SystemUI to Launcher.
|
||||
*/
|
||||
public class ProxyScreenStatusProvider implements ScreenStatusProvider {
|
||||
|
||||
public static final ProxyScreenStatusProvider INSTANCE = new ProxyScreenStatusProvider();
|
||||
private final List<ScreenListener> mListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Called when the screen is on and ready (windows are drawn and screen blocker is removed)
|
||||
*/
|
||||
public void onScreenTurnedOn() {
|
||||
mListeners.forEach(ScreenListener::onScreenTurnedOn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(@NonNull ScreenListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallback(@NonNull ScreenListener listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2021 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;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.widget.NavigableAppWidgetHostView;
|
||||
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
|
||||
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
|
||||
import com.android.unfold.UnfoldTransitionProgressProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Animation that moves launcher icons and widgets from center to the sides (final position)
|
||||
*/
|
||||
public class UnfoldMoveFromCenterWorkspaceAnimator
|
||||
implements UnfoldTransitionProgressProvider.TransitionProgressListener {
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimation;
|
||||
|
||||
private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
|
||||
private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
|
||||
|
||||
public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
|
||||
mLauncher = launcher;
|
||||
mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
|
||||
new WorkspaceViewsTranslationApplier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionStarted() {
|
||||
mMoveFromCenterAnimation.updateDisplayProperties();
|
||||
|
||||
Workspace workspace = mLauncher.getWorkspace();
|
||||
Hotseat hotseat = mLauncher.getHotseat();
|
||||
|
||||
// App icons and widgets
|
||||
workspace
|
||||
.forEachVisiblePage(page -> {
|
||||
final CellLayout cellLayout = (CellLayout) page;
|
||||
ShortcutAndWidgetContainer itemsContainer = cellLayout
|
||||
.getShortcutsAndWidgets();
|
||||
disableClipping(cellLayout);
|
||||
|
||||
for (int i = 0; i < itemsContainer.getChildCount(); i++) {
|
||||
View child = itemsContainer.getChildAt(i);
|
||||
mMoveFromCenterAnimation.registerViewForAnimation(child);
|
||||
}
|
||||
});
|
||||
|
||||
disableClipping(workspace);
|
||||
|
||||
// Hotseat icons
|
||||
ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
|
||||
disableClipping(hotseat);
|
||||
|
||||
for (int i = 0; i < hotseatIcons.getChildCount(); i++) {
|
||||
View child = hotseatIcons.getChildAt(i);
|
||||
mMoveFromCenterAnimation.registerViewForAnimation(child);
|
||||
}
|
||||
|
||||
onTransitionProgress(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionProgress(float progress) {
|
||||
mMoveFromCenterAnimation.onTransitionProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
mMoveFromCenterAnimation.onTransitionFinished();
|
||||
mMoveFromCenterAnimation.clearRegisteredViews();
|
||||
|
||||
restoreClipping(mLauncher.getWorkspace());
|
||||
mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page));
|
||||
restoreClipping(mLauncher.getHotseat());
|
||||
|
||||
mOriginalClipChildren.clear();
|
||||
mOriginalClipToPadding.clear();
|
||||
}
|
||||
|
||||
private void disableClipping(ViewGroup view) {
|
||||
mOriginalClipToPadding.put(view, view.getClipToPadding());
|
||||
mOriginalClipChildren.put(view, view.getClipChildren());
|
||||
view.setClipToPadding(false);
|
||||
view.setClipChildren(false);
|
||||
}
|
||||
|
||||
private void restoreClipping(ViewGroup view) {
|
||||
final Boolean originalClipToPadding = mOriginalClipToPadding.get(view);
|
||||
if (originalClipToPadding != null) {
|
||||
view.setClipToPadding(originalClipToPadding);
|
||||
}
|
||||
final Boolean originalClipChildren = mOriginalClipChildren.get(view);
|
||||
if (originalClipChildren != null) {
|
||||
view.setClipChildren(originalClipChildren);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WorkspaceViewsTranslationApplier implements TranslationApplier {
|
||||
|
||||
@Override
|
||||
public void apply(@NonNull View view, float x, float y) {
|
||||
if (view instanceof NavigableAppWidgetHostView) {
|
||||
((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
|
||||
} else if (view instanceof BubbleTextView) {
|
||||
((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
|
||||
} else {
|
||||
view.setTranslationX(x);
|
||||
view.setTranslationY(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,6 +90,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
|||
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
|
||||
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
|
||||
|
||||
private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
|
||||
|
||||
private float mScaleForReorderBounce = 1f;
|
||||
|
||||
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
|
||||
|
@ -799,8 +801,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
|||
}
|
||||
|
||||
private void updateTranslation() {
|
||||
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
|
||||
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
|
||||
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
|
||||
+ mTranslationForMoveFromCenterAnimation.x);
|
||||
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
|
||||
+ mTranslationForMoveFromCenterAnimation.y);
|
||||
}
|
||||
|
||||
public void setReorderBounceOffset(float x, float y) {
|
||||
|
@ -833,6 +837,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
|||
return mScaleForReorderBounce;
|
||||
}
|
||||
|
||||
public void setTranslationForMoveFromCenterAnimation(float x, float y) {
|
||||
mTranslationForMoveFromCenterAnimation.set(x, y);
|
||||
updateTranslation();
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
|||
*/
|
||||
private final PointF mTranslationForCentering = new PointF(0, 0);
|
||||
|
||||
private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
|
||||
|
||||
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
|
||||
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
|
||||
private float mScaleForReorderBounce = 1f;
|
||||
|
@ -167,9 +169,9 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
|||
|
||||
private void updateTranslation() {
|
||||
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
|
||||
+ mTranslationForCentering.x);
|
||||
+ mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x);
|
||||
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
|
||||
+ mTranslationForCentering.y);
|
||||
+ mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y);
|
||||
}
|
||||
|
||||
public void setTranslationForCentering(float x, float y) {
|
||||
|
@ -177,6 +179,11 @@ public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
|
|||
updateTranslation();
|
||||
}
|
||||
|
||||
public void setTranslationForMoveFromCenterAnimation(float x, float y) {
|
||||
mTranslationForMoveFromCenterAnimation.set(x, y);
|
||||
updateTranslation();
|
||||
}
|
||||
|
||||
public void setReorderBounceOffset(float x, float y) {
|
||||
mTranslationForReorderBounce.set(x, y);
|
||||
updateTranslation();
|
||||
|
|
Loading…
Reference in New Issue